From d7d2aff8e71d0b3ed264aec0c9d18abe1e435c86 Mon Sep 17 00:00:00 2001 From: bzt Date: Wed, 30 Dec 2020 20:34:42 +0100 Subject: [PATCH] Additional BPs --- Makefile | 25 + bootboot.asm | 2916 +++++++++++++++++++++++++++++++++++++++++++++++ bootboot.inc | 125 ++ disk-x86.img.gz | Bin 0 -> 188724 bytes fs.inc | 656 +++++++++++ tinf.inc | 500 ++++++++ 6 files changed, 4222 insertions(+) create mode 100644 Makefile create mode 100644 bootboot.asm create mode 100644 bootboot.inc create mode 100644 disk-x86.img.gz create mode 100644 fs.inc create mode 100644 tinf.inc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d5a5919 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +all: bootboot.bin bootboot.sym refresh + +bootboot.bin: bootboot.asm + @echo " src x86_64-bios (MultiBoot / BIOS)" + @cat bootboot.asm | grep -v "^public" | grep -v "format ELF64" >bb.asm + @fasm bb.asm bootboot.bin >/dev/null + @rm bb.asm + +bootboot.sym: bootboot.asm + @echo " sym bootboot.sym" + @fasm bootboot.asm bootboot.elf >/dev/null + @nm bootboot.elf | sort | sed 's/\ A\ /\ /g' > bootboot.sym + @printf "fffffffff8000000 mmio\nfffffffffc000000 fb\nffffffffffe00000 bootboot\nffffffffffe01000 environment\nffffffffffe02000 _start\n" >>bootboot.sym + @rm bootboot.elf + +disk-x86.img: disk-x86.img.gz + @gzip -d -k disk-x86.img.gz + +refresh: bootboot.bin disk-x86.img + @echo " dd bootboot.bin to disk-x86.img" + @dd if=bootboot.bin of=disk-x86.img bs=1 seek=120832 conv=notrunc 2>/dev/null + +clean: + @rm bootboot.bin bootboot.sym disk-x86.img >/dev/null 2>/dev/null || true + diff --git a/bootboot.asm b/bootboot.asm new file mode 100644 index 0000000..ed374ff --- /dev/null +++ b/bootboot.asm @@ -0,0 +1,2916 @@ +;* +;* x86_64-bios/bootboot.asm +;* +;* Copyright (C) 2017 - 2020 bzt (bztsrc@gitlab) +;* +;* Permission is hereby granted, free of charge, to any person +;* obtaining a copy of this software and associated documentation +;* files (the "Software"), to deal in the Software without +;* restriction, including without limitation the rights to use, copy, +;* modify, merge, publish, distribute, sublicense, and/or sell copies +;* of the Software, and to permit persons to whom the Software is +;* furnished to do so, subject to the following conditions: +;* +;* The above copyright notice and this permission notice shall be +;* included in all copies or substantial portions of the Software. +;* +;* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +;* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +;* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;* DEALINGS IN THE SOFTWARE. +;* +;* This file is part of the BOOTBOOT Protocol package. +;* @brief Booting code for BIOS, MultiBoot, El Torito, Linux boot +;* +;* Stage2 loader, compatible with GRUB and BIOS boot specification +;* 1.0.1 (even expansion ROM), El Torito "no emulation" CDROM boot, +;* as well as Linux boot protocol. +;* +;* text segment occupied: 800-7C00, bss: 8000-x +;* +;* Memory map +;* 0h - 600h reserved for the system +;* 600h - 800h stage1 (MBR/VBR, boot.bin) +;* 800h - 6C00h stage2 (this) +;* 6C00h - 7C00h stack (7000h - 700Fh SMP trampoline code) +;* 8000h - 9000h bootboot structure +;* 9000h - A000h environment +;* A000h - B000h disk buffer / PML4 +;* B000h - C000h PDPE, higher half core 4K slots +;* C000h - D000h PDE 4K +;* D000h - E000h PTE 4K +;* E000h - F000h PDPE, 4G physical RAM identity mapped 2M +;* F000h - 10000h PDE 2M +;* 10000h - 11000h PDE 2M +;* 11000h - 12000h PDE 2M +;* 12000h - 13000h PDE 2M +;* 13000h - 14000h PTE 4K +;* 14000h - 9F000h core stacks (1k per core) +;* +;* At first big enough free hole, initrd. Usually at 1Mbyte. +;* +;* WARNING: supports BOOTBOOT Protocol level 1 only (static mappings) +;* + +BBDEBUG equ 1 + +format ELF64 + +;get Core boot parameter block +include "bootboot.inc" + +;VBE filter (available, has additional info, color, graphic, linear fb) +VBE_MODEFLAGS equ 1+2+8+16+128 + +;********************************************************************* +;* Macros * +;********************************************************************* + +;Writes a message on screen. +macro real_print msg +{ +if ~ msg eq si + push si + mov si, msg +end if + call real_printfunc +if ~ msg eq si + pop si +end if +} + +;protected and real mode are functions, because we have to switch beetween +macro real_protmode +{ + USE16 + call near real_protmodefunc + USE32 +} + +macro prot_realmode +{ + USE32 + call near prot_realmodefunc + USE16 +} + +;edx:eax sector, edi:pointer +macro prot_readsector +{ + call near prot_readsectorfunc +} + +macro DBG msg +{ +if BBDEBUG eq 1 + real_print msg +end if +} + +macro DBG32 msg +{ +if BBDEBUG eq 1 + prot_realmode + real_print msg + real_protmode +end if +} + +virtual at 0 + bpb.jmp db 3 dup 0 + bpb.oem db 8 dup 0 + bpb.bps dw 0 + bpb.spc db 0 + bpb.rsc dw 0 + bpb.nf db 0 ;16 + bpb.nr dw 0 + bpb.ts16 dw 0 + bpb.media db 0 + bpb.spf16 dw 0 ;22 + bpb.spt dw 0 + bpb.nh dw 0 + bpb.hs dd 0 + bpb.ts32 dd 0 + bpb.spf32 dd 0 ;36 + bpb.flg dd 0 + bpb.rc dd 0 ;44 + bpb.vol db 6 dup 0 + bpb.fst db 8 dup 0 ;54 + bpb.dmy db 20 dup 0 + bpb.fst2 db 8 dup 0 ;84 +end virtual + +virtual at 0 + fatdir.name db 8 dup 0 + fatdir.ext db 3 dup 0 + fatdir.attr db 9 dup 0 + fatdir.ch dw 0 + fatdir.attr2 dd 0 + fatdir.cl dw 0 + fatdir.size dd 0 +end virtual + +;********************************************************************* +;* header * +;********************************************************************* +;offs len desc +; 0 2 expansion ROM magic (AA55h) +; 2 1 size in blocks (40h) +; 3 1 magic E9h +; 4 2 real mode entry point (relative) +; 6 2 checksum +; 8 8 magic 'BOOTBOOT' +; 16 10 zeros, at least one and a padding +; 26 2 pnp ptr, must be zero +; 28 4 flags, must be zero +; 32 32 MultiBoot header with protected mode entry point +; 64 x free to use +;497 127 Linux x86 boot protocol header +;any format can follow. + + USE16 + ORG 800h +;BOOTBOOT stage2 header (64 bytes) +public loader +loader: db 55h,0AAh ;ROM magic + db (loader_end-loader)/512 ;size in 512 blocks +.executor: jmp near realmode_start ;entry point +.checksum: dw 0 ;checksum +.name: db "BOOTBOOT" + dw 0 + dd 0, 0 +.pnpptr: dw 0 +.flags: dd 0 +MB_MAGIC equ 01BADB002h +MB_FLAGS equ 010001h + align 8 +.mb_header: dd MB_MAGIC ;magic + dd MB_FLAGS ;flags + dd -(MB_MAGIC+MB_FLAGS) ;checksum (0-magic-flags) + dd .mb_header ;our location (GRUB should load us here) + dd 0800h ;the same... load start + dd 07C00h ;load end + dd 0h ;no bss + dd multiboot_start ;entry point + +;no segments or sections, code comes right after the header + +;********************************************************************* +;* code * +;********************************************************************* + +;----------------Multiboot stub----------------- + USE32 +public multiboot_start +multiboot_start: + cli + cld + ;clear drive code, initrd ptr and environment + xor edx, edx + mov edi, 9000h + mov dword [edi], edx + mov dword [bootboot.initrd_ptr], edx + mov dword [bootboot.initrd_size], edx + ;no GRUB environment available? + cmp eax, 2BADB002h + jne @f + ;save drive code for boot device + mov dl, byte [ebx+12] + ;is there a module? mod_count!=0 + cmp dword [ebx+20], 0 + jz @f + ;mod_addr!=0 + mov eax, dword [ebx+24] + or eax, eax + jz @f + ;mods[0].end + mov ecx, dword [eax+4] + sub ecx, dword [eax] + mov dword [bootboot.initrd_size], ecx + ;mods[0].start + mov ecx, dword [eax] + mov dword [bootboot.initrd_ptr], ecx + inc byte [hasinitrd] + ;mod_count>1? + cmp dword [ebx+20], 1 + jbe @f + ;mods[1], copy environment (4k) + mov esi, dword [eax+16] + mov ecx, 1024 + repnz movsd + inc byte [hasconfig] +@@: lgdt [GDT_value] + mov ax, DATA_BOOT ;clear shadow segment registers + mov ds, ax + mov es, ax + mov ss, ax + xor esp, esp ;GRUB leaves the upper 16 bits non-zero, we must clear it + jmp CODE_BOOT:.real ;load 16 bit mode segment into cs + USE16 +.real: mov eax, CR0 + and eax, 07FFFFFFEh ;switching back to real mode + mov CR0, eax + xor ax, ax + mov ds, ax ;load segment registers DS and CS + jmp 0:@f +@@: lidt [idt16] ;restore IDT as newer GRUBs mess it up + ;fallthrough realmode_start + +;-----------realmode-protmode stub------------- +public realmode_start +realmode_start: + cli + cld + xchg bx, bx + mov sp, 7C00h + xor ax, ax + mov es, ax + mov ss, ax + ;relocate ourself from ROM to RAM if necessary + call .getaddr +.getaddr: pop si + mov ax, cs + or ax, ax + jnz .reloc + cmp si, .getaddr + je .noreloc +.reloc: mov ds, ax + mov di, loader + sub si, .getaddr-loader + mov cx, (loader_end-loader)/2 + repnz movsw + xor ax, ax + mov ds, ax + jmp 0:.clrdl +.noreloc: or dl, dl + jnz @f +.clrdl: mov dl, 80h +@@: mov byte [bootdev], dl + + ;-----initialize serial port COM1,115200,8N1------ + mov ax, 0401h + xor bx, bx + mov cx, 030Bh + xor dx, dx + int 14h + ; if there's no serial, but BIOS incorrectly sets the IO port for it + mov dx, word [400h] + or dx, dx + jz @f + add dx, 5 + in al, dx + cmp al, 0FFh + jne @f + mov word [400h], 0 +@@: real_print starting + + ; flush PS/2 keyboard + in al, 060h ; read key + in al, 061h ; ack + out 061h, al + + DBG dbg_cpu + + ;-----check CPU----- + ;at least 286? + pushf + pushf + pop dx + xor dh,40h + push dx + popf + pushf + pop bx + popf + cmp dx, bx + jne .cpuerror + mov ebp, 200000h + ;check for 386 + ;look for cpuid instruction + pushfd + pop eax + mov ebx, eax + xor eax, ebp + and ebx, ebp + push eax + popfd + pushfd + pop eax + and eax, ebp + xor eax, ebx + shr eax, 21 + and al, 1 + jz .cpuerror + ;ok, now we can get cpu feature flags + mov eax, 1 + cpuid + shr al, 4 + shr ebx, 24 + mov word [bootboot.bspid], bx + ;look for minimum family + cmp ax, 0600h + jb .cpuerror + ;look for minimum feature flags + ;so we have PAE? + bt edx, 6 + jnc .cpuerror + ;what about MSR? + bt edx, 5 + jnc .cpuerror + ;and can we use long mode (LME)? + mov eax, 80000000h + mov ebp, eax + inc ebp + cpuid + cmp eax, ebp + jb .cpuerror + mov eax, ebp + cpuid + ;long mode + bt edx, 29 + jc cpuok +.cpuerror: mov si, noarch + jmp real_diefunc + ;okay, we can do 64 bit! + +;--- Linux x86 boot protocol header --- + db 01F1h-($-$$) dup 090h +public hdr +hdr: +setup_sects: db (loader_end-loader-511)/512 +root_flags: dw 0 +syssize: dd (loader_end-loader)/16 +ram_size: dw 0 +vid_mode: dw 0 +root_dev: dw 0 +boot_flag: dw 0AA55h + db 0EBh ; short jmp + db start_of_setup-@f ; no space to jump to realmode_start directly +@@: db "HdrS" + dw 020eh +realmode_swtch: dd 0 +start_sys_seg: dw 0 + dw 0 ; we don't have Linux kernel version... +type_of_loader: db 0FFh +loadflags: db 0 +setup_move_size: dw 08000h +code32_start: dd 0 ; we dont' use this either... +ramdisk_image: dd 0 +ramdisk_size: dd 0 +bootsect_kludge: dd 0 +heap_end_ptr: dd loader_end+1024-512 ; we don't care, we set our stack directly +ext_loader_ver: db 0 +ext_loader_type: db 0 +cmd_line_ptr: dd 0 +initrd_addr_max: dd 07fffffffh +kernel_alignment: dd 16 +relocatable_kernel: db 0 +min_alignment: db 4 +xloadflags: dw 0 +cmdline_size: dd 0 +hardware_subarch: dd 0 +hardware_subarch_data: dq 0 +payload_offset: dd 0 +payload_length: dd 0 +setup_data: dq 0 +pref_address: dq 90000h ; qemu does not handle anything else +init_size: dd loader_end-loader +handover_offset: dd 0 +acpi_rsdp_addr: dq 0 +start_of_setup: + ; fix: qemu forces address and set CS to 0x9020, but we must jump to segment 0x9000. + jmp 9000h:realmode_start-loader +; --- end of Linux boot protocol header --- + +public cpuok +cpuok: DBG dbg_A20 + + ;-----enable A20----- + ;no problem even if a20 is already turned on. + mov ax, 2401h ;BIOS enable A20 function + int 15h + ;see if it worked + call a20chk + jz a20ok + ;keyboard nightmare + call a20wait + mov al, 0ADh + out 64h, al + call a20wait + mov al, 0D0h + out 64h, al + call a20wait2 + in al, 60h + push ax + call a20wait + mov al, 0D1h + out 64h, al + call a20wait + pop ax + or al, 2 + out 60h, al + call a20wait + mov al, 0AEh + out 64h, al + call a20wait + call a20chk + jz a20ok + ;fast enable method + in al, 92h + test al, 2 + jnz a20ok + or al, 2 + and al, 0FEh + out 92h, al + jmp a20ok + +public a20chk +a20chk: push es + xor ax, ax + dec ax + mov es, ax + mov ah, byte [es:510h] + mov byte [ds:500h], 0 + mov byte [es:510h], al + mov al, byte [ds:500h] + mov byte [es:510h], ah + pop es + or al, al + ret +a20wait: in al, 64h + test al, 2 + jnz a20wait + ret +a20wait2: in al, 64h + test al, 1 + jz a20wait2 + ret +a20ok: + ; wait for a key press for half a sec, if pressed use backup initrd + mov word [origcount], 0 + sti +.waitkey: mov ax, word 0100h + or al, al ; make sure ZF is clear + int 16h + jz @f + or al, al + jz @f + ; this blocks, so only call it if we are sure there's a keystroke waiting + xor ah, ah + int 16h + mov eax, ' BAK' + mov dword [bkp], eax + real_print backup + jmp .waitend +@@: ; wait 10 millisec + xor cx, cx + mov dx, 10000 + mov ah, 086h + int 15h + ; repeat loop 50 times + inc word [origcount] + cmp word [origcount], 50 + jl .waitkey +.waitend: cli + + ;-----detect memory map----- +public getmemmap +getmemmap: + DBG dbg_mem + + xor eax, eax + mov edi, bootboot.acpi_ptr + mov ecx, 16 + repnz stosd + cmp byte [hasconfig], 0 + jnz @f + mov dword [9000h], eax +@@: cmp byte [hasinitrd], 0 + jnz @f + mov dword [bootboot.initrd_ptr], eax + mov dword [bootboot.initrd_size], eax +@@: mov dword [bootboot.initrd_ptr+4], eax + mov dword [bootboot.initrd_size+4], eax + mov eax, bootboot_MAGIC + mov dword [bootboot.magic], eax + mov dword [bootboot.size], 128 + mov dword [bootboot.protocol], PROTOCOL_STATIC or LOADER_BIOS + mov di, bootboot.fb_ptr + mov cx, 800h-28h + xor ax, ax + repnz stosw + mov di, bootboot.mmap + xor ebx, ebx + clc +.nextmap: cmp word [bootboot.size], 4096 + jae .nomoremap + mov edx, 'PAMS' + xor ecx, ecx + mov cl, 20 + xor eax, eax + mov ax, 0E820h + int 15h + jc .nomoremap + cmp eax, 'PAMS' + jne .nomoremap + ;is this the first memory hole? If so, mark + ;ourself as reserved memory + cmp dword [di+4], 0 + jnz .notfirst + cmp dword [di], 0 + jnz .notfirst + ; "allocate" memory for loader + mov eax, 14000h + add dword [di], eax + sub dword [di+8], eax + ;convert E820 memory type to BOOTBOOT memory type +.notfirst: mov al, byte [di+16] + cmp al, 1 + je .noov + cmp al, 4 + je .isacpi + cmp al, 3 + jne @f +.isacpi: mov al, MMAP_ACPI + jmp .noov + ; everything else reserved +@@: mov al, MMAP_USED +.noov: ;copy memory type to size's least significant byte + mov byte [di+8], al + xor ecx, ecx + ;is it ACPI area? + cmp al, MMAP_ACPI + jne .notacpi + mov dword [bootboot.acpi_ptr], edi + jmp .entryok + ;is it free slot? +.notacpi: cmp al, MMAP_FREE + jne .notmax +.freemem: ;do we have a ramdisk area? + cmp dword [bootboot.initrd_ptr], 0 + jnz .entryok + ;is it big enough for the compressed and the inflated ramdisk? + mov ebp, (INITRD_MAXSIZE+2)*1024*1024 + shr ebp, 20 + shl ebp, 20 + ;is this free memory hole big enough? (first fit) +.sizechk: mov eax, dword [di+8] ;load size + xor al, al + mov edx, dword [di+12] + and edx, 000FFFFFFh + or edx, edx + jnz .bigenough + cmp eax, ebp + jb .entryok +.bigenough: mov eax, dword [di] + ;save ramdisk pointer + mov dword [bootboot.initrd_ptr], eax +.entryok: ;get limit of memory + mov eax, dword [di+8] ;load size + xor al, al + mov edx, dword [di+12] + add eax, dword [di] ;add base + adc edx, dword [di+4] + and edx, 000FFFFFFh +.notmax: add dword [bootboot.size], 16 + ;bubble up entry if necessary + push si + push di +.bubbleup: mov si, di + cmp di, bootboot.mmap + jbe .swapdone + sub di, 16 + ;order by base asc + mov eax, dword [si+4] + cmp eax, dword [di+4] + jb .swapmodes + jne .swapdone + mov eax, dword [si] + cmp eax, dword [di] + jae .swapdone +.swapmodes: push di + mov cx, 16/2 +@@: mov dx, word [di] + lodsw + stosw + mov word [si-2], dx + dec cx + jnz @b + pop di + jmp .bubbleup +.swapdone: pop di + pop si + add di, 16 + cmp di, bootboot.mmap+4096 + jae .nomoremap +.skip: or ebx, ebx + jnz .nextmap +.nomoremap: cmp dword [bootboot.size], 128 + jne .E820ok +.noE820: mov si, memerr + jmp real_diefunc + +.E820ok: ;check if we have memory for the ramdisk + xor ecx, ecx + cmp dword [bootboot.initrd_ptr], ecx + jnz .enoughmem +.nomem: mov si, noenmem + jmp real_diefunc +.enoughmem: + +public systables +systables: + ;-----detect system structures----- + DBG dbg_systab + + ;do we need that scanning shit? + mov eax, dword [bootboot.acpi_ptr] + or eax, eax + jz @f + shr eax, 4 + mov es, ax + ;no if E820 map was correct + cmp dword [es:0], 'XSDT' + je .detsmbi + cmp dword [es:0], 'RSDT' + je .detsmbi +@@: inc dx + ;get starting address min(EBDA,E0000) + mov ah,0C1h + stc + int 15h + mov bx, es + jnc @f + mov ax, [ebdaptr] +@@: cmp ax, 0E000h + jb .acpinext + mov ax, 0E000h + ;detect ACPI ptr +.acpinext: mov es, ax + cmp dword [es:0], 'RSD ' + jne .acpinotf + cmp dword [es:4], 'PTR ' + jne .acpinotf + ;ptr found + ; do we have XSDT? + cmp dword [es:28], 0 + jne .acpi2 + cmp dword [es:24], 0 + je .acpi1 +.acpi2: mov eax, dword [es:24] + mov dword [bootboot.acpi_ptr], eax + mov edx, dword [es:28] + mov dword [bootboot.acpi_ptr+4], edx + jmp .chkacpi + ; no, fallback to RSDT +.acpi1: mov eax, dword [es:16] +@@: mov dword [bootboot.acpi_ptr], eax + xor edx, edx + jmp .chkacpi +.acpinotf: xor eax, eax + mov ax, es + inc ax + cmp ax, 0A000h + jne @f + add ax, 03000h +@@: ;end of 1Mb? + or ax, ax + jnz .acpinext + mov si, noacpi + jmp real_diefunc +.chkacpi: ; check if memory is marked as ACPI in the memory map + mov esi, bootboot.mmap + mov edi, bootboot.magic + add edi, dword [bootboot.size] +.nextmm: cmp edx, dword [si+4] + jne .skipmm + mov ebx, dword [si] + cmp eax, ebx + jl .skipmm + add ebx, dword [si+8] + and bl, 0F0h + cmp eax, ebx + jge .skipmm + mov al, byte [si+8] + and al, 0F0h + or al, MMAP_ACPI + mov byte [si+8], al + jmp .detsmbi +.skipmm: add si, 16 + cmp si, di + jl .nextmm + + ;detect SMBios tables +.detsmbi: xor eax, eax + mov ax, 0E000h + xor dx, dx +.smbnext: mov es, ax + push ax + cmp dword [es:0], '_SM_' + je .smbfound + cmp dword [es:0], '_MP_' + jne .smbnotf + shl eax, 4 + mov dword [bootboot.mp_ptr], eax + bts dx, 2 + jmp .smbnotf +.smbfound: shl eax, 4 + mov dword [bootboot.smbi_ptr], eax + bts dx, 1 +.smbnotf: pop ax + bt ax, 0 + mov bx, ax + and bx, 03h + inc ax + ;end of 1Mb? + or ax, ax + jnz .smbnext + ;restore ruined es +.detend: push ds + pop es + + DBG dbg_time + + ; ------- BIOS date and time ------- + mov ah, 4 + int 1Ah + jc .nobtime + ;ch century + ;cl year + xchg ch, cl + mov word [bootboot.datetime], cx + ;dh month + ;dl day + xchg dh, dl + mov word [bootboot.datetime+2], dx + mov ah, 2 + int 1Ah + jc .nobtime + ;ch hour + ;cl min + xchg ch, cl + mov word [bootboot.datetime+4], cx + ;dh sec + ;dl daylight saving on/off + xchg dh, dl + mov word [bootboot.datetime+6], dx +.nobtime: + + ;---- enable protmode ---- + cli + cld + lgdt [GDT_value] + mov eax, cr0 + or al, 1 + mov cr0, eax + jmp CODE_PROT:protmode_start + + ;---- enable protmode on APs ---- +public ap_trampoline +ap_trampoline: + ;--this code will be relocated to the SIPI address -- + cli + cld + jmp 0:ap_start + ;--relocation end-- +ap_start: xor ax, ax + mov ds, ax + lgdt [GDT_value] + mov eax, cr0 + or al, 1 + mov cr0, eax + jmp CODE_PROT:@f + USE32 +@@: mov ax, DATA_PROT + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + ; spinlock until BSP finishes +@@: pause + cmp byte [bsp_done], 0 + jz @b + jmp longmode_init + +;writes the reason, waits for a key and reboots. +public prot_diefunc +prot_diefunc: + prot_realmode + USE16 +public real_diefunc +real_diefunc: + push si + real_print loader.name + real_print panic + pop si + call real_printfunc + mov si, crlf + call real_printfunc + call real_getchar + mov al, 0FEh + out 64h, al + jmp far 0FFFFh:0 ;invoke BIOS POST routine + +; get a character from keyboard or from serial line +public real_getchar +real_getchar: + pushf + sti + push si + push di +.chkser: cmp word [400h], 0 + jz @f + mov ah, byte 03h + xor dx, dx + int 14h + bt ax, 8 + jnc @f + mov ah, byte 02h + xor dx, dx + int 14h + jmp .gotch +@@: mov ah, byte 01h + int 16h + jz .chkser + xor ah, ah + int 16h +.gotch: pop di + pop si + popf + xor ah, ah + ret + +;ds:si zero terminated string to write +public real_printfunc +real_printfunc: + lodsb + or al, al + jz .end + push si + push ax + mov ah, byte 0Eh + mov bx, word 11 + int 10h + pop ax + cmp word [400h], 0 + jz @f + mov ah, byte 01h + xor dx, dx + int 14h +@@: pop si + jmp real_printfunc +.end: ret + +public real_protmodefunc +real_protmodefunc: + cli + ;get return address + xor ebp, ebp + pop bp + mov dword [hw_stack], esp + lgdt [GDT_value] + mov eax, cr0 ;enable protected mode + or al, 1 + mov cr0, eax + jmp CODE_PROT:.init + + USE32 +.init: mov ax, DATA_PROT + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov esp, dword [hw_stack] + jmp ebp + +public prot_realmodefunc +prot_realmodefunc: + cli + ;get return address + pop ebp + ;save stack pointer + mov dword [hw_stack], esp + jmp CODE_BOOT:.back ;load 16 bit mode segment into cs + USE16 +.back: mov eax, CR0 + and al, 0FEh ;switching back to real mode + mov CR0, eax + xor ax, ax + mov ds, ax ;load registers 2nd turn + mov es, ax + mov ss, ax + jmp 0:.back2 +.back2: mov sp, word [hw_stack] + sti + jmp bp + + USE32 +public prot_readsectorfunc +prot_readsectorfunc: + push eax + push ecx + push esi + push edi + ;load 8 sectors (1 page) or more in low memory + mov dword [lbapacket.sect0], eax + prot_realmode + ;try all drives from bootdev-87 to support RAID mirror + mov dl, byte [bootdev] + mov byte [readdev], dl + mov byte [cntdev], 0 + mov byte [iscdrom], 0 +.again: mov ax, word [lbapacket.count] + mov word [origcount], ax + mov dl, byte [readdev] + ;don't use INT 13h / AX=4B01h, that's buggy on many BIOSes + cmp dl, 0E0h + jl @f + ;use 2048 byte sectors instead of 512 if it's a cdrom + mov al, byte [lbapacket.sect0] + and al, 011b + or al, al + jz .cdok + ;this should never happen. + ; - GPT is loaded from PMBR, from LBA 0 (%4==0) + ; - ESP is at LBA 128 or 2048 (%4==0) + ; - root dir is at LBA 172 (%4==0) for FAT16, and it's cluster aligned for FAT32 + ; - cluster size is multiple of 4 sectors + mov si, notcdsect + jmp real_diefunc +.cdok: shr dword [lbapacket.sect0], 2 + add word [lbapacket.count], 3 + shr word [lbapacket.count], 2 + mov byte [iscdrom], 1 +@@: mov ah, byte 42h + mov esi, lbapacket + clc + int 13h + jnc .rdok + ;we do not expect this to fail, but if so, load sector from another + ;drive assuming we have a RAID mirror. This will fail for non-RAIDs + mov al, byte [readdev] + inc al + cmp al, 87h + jle @f + mov al, 80h +@@: mov byte [readdev], al + inc byte [cntdev] + cmp byte [cntdev], 8 + jle .again +.rdok: xor ebx, ebx + mov bl, ah + real_protmode + pop edi + or edi, edi + jz @f + push edi + ;and copy to addr where it wanted to be (maybe in high memory) + mov esi, dword [lbapacket.addr] + xor ecx, ecx + mov cx, word [origcount] + shl ecx, 7 + repnz movsd + pop edi +@@: pop esi + pop ecx + pop eax + ret + +public prot_hex2bin +prot_hex2bin: + xor eax, eax + xor ebx, ebx +@@: mov bl, byte [esi] + cmp bl, '0' + jb @f + cmp bl, '9' + jbe .num + cmp bl, 'A' + jb @f + cmp bl, 'F' + ja @f + sub bl, 7 +.num: sub bl, '0' + shl eax, 4 + add eax, ebx + inc esi + dec cx + jnz @b +@@: ret + +public prot_dec2bin +prot_dec2bin: + xor eax, eax + xor ebx, ebx + xor edx, edx + mov ecx, 10 +@@: mov bl, byte [esi] + cmp bl, '0' + jb @f + cmp bl, '9' + ja @f + mul ecx + sub bl, '0' + add eax, ebx + inc esi + jmp @b +@@: ret + +;IN: eax=str ptr, ecx=length OUT: eax=num +public prot_oct2bin +prot_oct2bin: + push ebx + push edx + mov ebx, eax + xor eax, eax + xor edx, edx +@@: shl eax, 3 + mov dl, byte[ebx] + sub dl, '0' + add eax, edx + inc ebx + dec ecx + jnz @b + pop edx + pop ebx + ret + +; IN: al, character to send +public uart_send +uart_send: mov ah, al + mov dx, word [400h] ;3fdh + add dx, 5 +@@: pause + in al, dx + and al, 20h + jz @b + sub dx, 5 + mov al, ah + out dx, al + ret + +; IN: edi pointer to store the received char +public uart_getc +uart_getc: mov dx, word [400h] ;3fdh + add dx, 5 +@@: pause + in al, dx + and al, 1 + jz @b + sub dl, 5 + in al, dx + stosb + ret + +public protmode_start +protmode_start: + xchg bx, bx + mov ax, DATA_PROT + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov esp, 7C00h + + ; ------- Locate initrd -------- + cmp byte [hasinitrd], 0 + jnz .initrdrom + mov esi, 0C8000h +.nextrom: cmp word [esi], 0AA55h + jne @f + cmp dword [esi+8], 'INIT' + jne @f + cmp word [esi+12], 'RD' + jne @f + mov ecx, dword [esi+16] + mov dword [bootboot.initrd_size], ecx + add esi, 32 + cmp word [esi], 08b1fh + je .initrdrom + ; copy from ROM to RAM + mov edi, dword [bootboot.initrd_ptr] + repnz movsb + jmp .noinflate +@@: add esi, 2048 + cmp esi, 0F4000h + jb .nextrom + + ;---- notify raspbootcom / USBImager to send the initrd over serial line ---- + cmp word [400h], 0 + jz .getgpt + + mov al, 3 + call uart_send + call uart_send + call uart_send + + ; wait for response with timeout + mov ah, al + mov cx, 10000 + mov dx, word [400h] + add dx, 5 +@@: dec cx + jz .getgpt + pause + in al, dx + and al, 1 + jz @b + + DBG32 dbg_serial + + ; read the initrd's size + mov edi, bootboot.initrd_size + call uart_getc + call uart_getc + call uart_getc + call uart_getc + mov ecx, dword [bootboot.initrd_size] + ; send negative or positive acknowledge + cmp ecx, 32 + jb .se + cmp ecx, INITRD_MAXSIZE*1024*1024 + jb .ok +.se: mov al, 'S' + call uart_send + mov al, 'E' + call uart_send + jmp .getgpt +.ok: mov al, 'O' + call uart_send + mov al, 'K' + call uart_send + mov edi, dword [bootboot.initrd_ptr] + ; read in the image +@@: call uart_getc + dec ecx + jnz @b + jmp .initrdloaded + + ;---- read GPT ----- +.getgpt: xor eax, eax + xor edi, edi + prot_readsector +if BBDEBUG eq 1 + cmp byte [iscdrom], 0 + jz @f + DBG32 dbg_cdrom + jmp .isgpt +@@: DBG32 dbg_gpt +.isgpt: +end if + mov esi, 0A000h+512 + cmp dword [esi], 'EFI ' + je @f +.nogpt: mov esi, nogpt + jmp prot_diefunc +@@: mov edi, 0B000h + mov ebx, edi + mov ecx, 896 + repnz movsd + mov esi, ebx + mov ecx, dword [esi+80] ;number of entries + mov ebx, dword [esi+84] ;size of one entry + add esi, 512 + mov edx, esi ;first entry + mov dword [gpt_ent], ebx + mov dword [gpt_num], ecx + mov dword [gpt_ptr], edx + ; first, look for a partition with bootable flag + mov esi, edx +@@: cmp dword [esi], 0 ;failsafe, jump to parttype search + jne .notz + cmp dword [esi+32], 0 + jz .nextgpt +.notz: bt word [esi+48], 2 ;EFI_PART_USED_BY_OS? + jc .loadesp2 + add esi, ebx + dec ecx + jnz @b + ; if none, look for specific partition types +.nextgpt: mov esi, dword [gpt_ptr] + mov ebx, dword [gpt_ent] + mov ecx, dword [gpt_num] + mov eax, 0C12A7328h + mov edx, 011D2F81Fh +@@: cmp dword [esi], eax ;GUID match? + jne .note + cmp dword [esi+4], edx + je .loadesp +.note: cmp dword [esi], 'OS/Z' ;or OS/Z root partition for this architecture? + jne .noto + cmp word [esi+4], 08664h + jne .noto + cmp dword [esi+12], 'root' + je .loadesp +.noto: add esi, ebx + dec ecx + jnz @b +.nopart: mov esi, nopar + jmp prot_diefunc + + ; load ESP at free memory hole found +.loadesp: mov dword [gpt_ptr], esi + mov dword [gpt_num], ecx +.loadesp2: mov ecx, dword [esi+40] ;last sector + mov eax, dword [esi+32] ;first sector + mov edx, dword [esi+36] + or edx, edx + jnz .nextgpt + or ecx, ecx + jz .nextgpt + or eax, eax + jz .nextgpt + mov dword [bpb_sec], eax + ;load BPB + mov edi, dword [bootboot.initrd_ptr] + mov word [lbapacket.count], 8 + prot_readsector + + ;parse fat on EFI System Partition +@@: cmp dword [edi + bpb.fst2], 'FAT3' + je .isfat + cmp dword [edi + bpb.fst], 'FAT1' + je .isfat + ;no, then it's an initrd on the entire partition + or eax, eax + jz .nopart + or ecx, ecx + jz .nopart + sub ecx, eax + shl ecx, 9 + mov dword [bootboot.initrd_size], ecx + ; load INITRD from partition + dec ecx + shr ecx, 12 + mov edi, dword [bootboot.initrd_ptr] +@@: add edi, 4096 + prot_readsector + add eax, 8 + dec ecx + jnz @b + jmp .initrdloaded + +.isfat: cmp word [edi + bpb.bps], 512 + jne .nextgpt + ;calculations + xor eax, eax + xor ebx, ebx + xor ecx, ecx + xor edx, edx + mov bx, word [edi + bpb.spf16] + or bx, bx + jnz @f + mov ebx, dword [edi + bpb.spf32] +@@: mov al, byte [edi + bpb.nf] + mov cx, word [edi + bpb.rsc] + ;data_sec = numFat*secPerFat + mul ebx + ;data_sec += reservedSec + add eax, ecx + ;data_sec += ESPsec + add eax, dword [bpb_sec] + mov dword [data_sec], eax + mov dword [root_sec], eax + xor eax, eax + mov al, byte [edi + bpb.spc] + mov dword [clu_sec], eax + ;FAT16 + ;data_sec += (numRootEnt*32+511)/512 + cmp word [edi + bpb.spf16], 0 + je .fat32bpb + cmp byte [edi + bpb.fst + 4], '6' + jne .nextgpt + xor eax, eax + mov ax, word [edi + bpb.nr] + shl eax, 5 + add eax, 511 + shr eax, 9 + add dword [data_sec], eax + mov byte [fattype], 0 + xor ecx, ecx + mov cx, word [edi + bpb.spf16] + jmp .loadfat +.fat32bpb: ;FAT32 + ;root_sec += (rootCluster-2)*secPerCluster + mov eax, dword [edi + bpb.rc] + sub eax, 2 + xor edx, edx + mov ebx, dword [clu_sec] + mul ebx + add dword [root_sec], eax + mov byte [fattype], 1 + mov ecx, dword [edi + bpb.spf32] + ;load FAT +.loadfat: xor eax, eax + mov ax, word [edi+bpb.rsc] + add eax, dword [bpb_sec] + shr ecx, 3 + inc ecx + mov edi, 0x10000 + mov word [lbapacket.count], 8 +@@: prot_readsector + add edi, 4096 + add eax, 8 + dec ecx + jnz @b + mov ax, word [clu_sec] + mov word [lbapacket.count], ax + + ;load root directory + mov eax, dword [root_sec] + mov edi, dword [bootboot.initrd_ptr] + prot_readsector + + ;look for BOOTBOOT directory + mov esi, edi + mov eax, 'BOOT' + mov ecx, 255 +.nextroot: cmp dword [esi], eax + jne @f + cmp dword [esi+4], eax + jne @f + cmp word [esi+8], ' ' + je .foundroot +@@: add esi, 32 + cmp byte [esi], 0 + jz .nextgpt + dec ecx + jnz .nextroot + jmp .nextgpt +.foundroot: xor eax, eax + cmp byte [fattype], 0 + jz @f + mov ax, word [esi + fatdir.ch] + shl eax, 16 +@@: mov ax, word [esi + fatdir.cl] + ;sec = (cluster-2)*secPerCluster+data_sec + sub eax, 2 + mov ebx, dword [clu_sec] + mul ebx + add eax, dword [data_sec] + mov edi, dword [bootboot.initrd_ptr] + prot_readsector + + ;look for CONFIG and INITRD + mov esi, edi + mov ecx, 255 + mov edx, dword [bkp] +.nextdir: cmp dword [esi], 'CONF' + jne .notcfg + cmp dword [esi+4], 'IG ' + jne .notcfg + cmp dword [esi+7], ' ' + jne .notcfg + ; load environment from FS0:/BOOTBOOT/CONFIG + push edx + push esi + push edi + push ecx + mov ecx, dword [esi + fatdir.size] + cmp ecx, 4095 + jbe @f + mov ecx, 4095 +@@: mov dword [core_len], ecx + xor eax, eax + cmp byte [fattype], 0 + jz @f + mov ax, word [esi + fatdir.ch] + shl eax, 16 +@@: mov ax, word [esi + fatdir.cl] + mov edi, 9000h +.nextcfg: push eax + ;sec = (cluster-2)*secPerCluster+data_sec + sub eax, 2 + mov ebx, dword [clu_sec] + mov word [lbapacket.count], bx + mul ebx + shl ebx, 9 + add eax, dword [data_sec] + push ebx + prot_readsector + pop ebx + pop eax + add edi, ebx + sub ecx, ebx + js .cfgloaded + jz .cfgloaded + ;get next cluster from FAT + cmp byte [fattype], 0 + jz @f + shl eax, 2 + add eax, 0x10000 + mov eax, dword [eax] + jmp .nextcfg +@@: shl eax, 1 + add eax, 0x10000 + mov ax, word [eax] + and eax, 0FFFFh + jmp .nextcfg +.cfgloaded: pop ecx + pop edi + pop esi + pop edx + xor eax, eax + mov ecx, 4096 + sub ecx, dword [core_len] + mov edi, 9000h + add edi, dword [core_len] + repnz stosb + mov dword [core_len], eax + mov byte [9FFFh], al + jmp .notinit +.notcfg: + cmp dword [esi], 'X86_' + jne @f + cmp dword [esi+4], '64 ' + je .altinitrd +@@: cmp dword [esi], 'INIT' + jne .notinit + cmp dword [esi+4], 'RD ' + jne .notinit + cmp dword [esi+7], edx + jne .notinit + +.altinitrd: mov ecx, dword [esi + fatdir.size] + mov dword [bootboot.initrd_size], ecx + xor eax, eax + cmp byte [fattype], 0 + jz @f + mov ax, word [esi + fatdir.ch] + shl eax, 16 +@@: mov ax, word [esi + fatdir.cl] + jmp .loadinitrd + +.notinit: add esi, 32 + cmp byte [esi], 0 + jz .loadinitrd + dec ecx + jnz .nextdir +.noinitrd: mov esi, nord + jmp prot_diefunc + + ;load cluster chain, eax=cluster, ecx=size +.loadinitrd: + mov edi, dword [bootboot.initrd_ptr] +.nextclu: push eax + ;sec = (cluster-2)*secPerCluster+data_sec + sub eax, 2 + mov ebx, dword [clu_sec] + mov word [lbapacket.count], bx + mul ebx + shl ebx, 9 + add eax, dword [data_sec] + push ebx + prot_readsector + pop ebx + pop eax + add edi, ebx + sub ecx, ebx + js .initrdloaded + jz .initrdloaded + ;get next cluster from FAT + cmp byte [fattype], 0 + jz @f + shl eax, 2 + add eax, 0x10000 + mov eax, dword [eax] + jmp .nextclu +@@: shl eax, 1 + add eax, 0x10000 + mov ax, word [eax] + and eax, 0FFFFh + jmp .nextclu + +.initrdloaded: + DBG32 dbg_initrd + mov esi, dword [bootboot.initrd_ptr] +.initrdrom: + mov edi, dword [bootboot.initrd_ptr] + cmp word [esi], 08b1fh + jne .noinflate + DBG32 dbg_gzinitrd + mov ebx, esi + mov eax, dword [bootboot.initrd_size] + add ebx, eax + sub ebx, 4 + mov ecx, dword [ebx] + mov dword [bootboot.initrd_size], ecx + add edi, eax + add edi, 4095 + shr edi, 12 + shl edi, 12 + add eax, ecx + cmp eax, (INITRD_MAXSIZE+2)*1024*1024 + jb @f + mov esi, nogzmem + jmp prot_diefunc +@@: mov dword [bootboot.initrd_ptr], edi + ; inflate initrd + xor eax, eax + add esi, 2 + lodsb + cmp al, 8 + jne tinf_err + lodsb + mov bl, al + add esi, 6 + test bl, 4 + jz @f + lodsw + add esi, eax +@@: test bl, 8 + jz .noname +@@: lodsb + or al, al + jnz @b +.noname: test bl, 16 + jz .nocmt +@@: lodsb + or al, al + jnz @b +.nocmt: test bl, 2 + jz @f + add esi, 2 +@@: call tinf_uncompress +.noinflate: + ;round up to page size + mov eax, dword [bootboot.initrd_size] + add eax, 4095 + shr eax, 12 + shl eax, 12 + mov dword [bootboot.initrd_size], eax + + ;do we have an environment configuration? + mov ebx, 9000h + cmp byte [ebx], 0 + jnz .parsecfg + + ;-----load /sys/config------ + mov edx, fsdrivers +.nextfs1: xor ebx, ebx + mov bx, word [edx] + or bx, bx + jz .errfs1 + mov esi, dword [bootboot.initrd_ptr] + mov ecx, dword [bootboot.initrd_size] + add ecx, esi + mov edi, cfgfile + push edx + call ebx + pop edx + or ecx, ecx + jnz .fscfg + add edx, 2 + jmp .nextfs1 +.fscfg: mov edi, 9000h + add ecx, 3 + shr ecx, 2 + repnz movsd +.errfs1: + ;do we have an environment configuration? + mov ebx, 9000h + cmp byte [ebx], 0 + jz .noconf + + ;parse +.parsecfg: push ebx + DBG32 dbg_env + pop esi + jmp .getnext + + ;skip comments +.nextvar: cmp byte[esi], '#' + je .skipcom + cmp word[esi], '//' + jne @f + add esi, 2 +.skipcom: lodsb + cmp al, 10 + je .getnext + cmp al, 13 + je .getnext + or al, al + jz .parseend + cmp esi, 0A000h + ja .parseend + jmp .skipcom +@@: cmp word[esi], '/*' + jne @f +.skipcom2: inc esi + cmp word [esi-2], '*/' + je .getnext + cmp byte [esi], 0 + jz .parseend + cmp esi, 0A000h + ja .parseend + jmp .skipcom2 + ;only match on beginning of line +@@: cmp esi, 9000h + je @f + cmp byte [esi-1], ' ' + je @f + cmp byte [esi-1], 13 + jae .next + ;parse screen dimensions +@@: cmp dword[esi], 'scre' + jne @f + cmp word[esi+4], 'en' + jne @f + cmp byte[esi+6], '=' + jne @f + add esi, 7 + call prot_dec2bin + mov dword [reqwidth], eax + inc esi + call prot_dec2bin + mov dword [reqheight], eax + jmp .getnext + ;get kernel's filename +@@: cmp dword[esi], 'kern' + jne @f + cmp word[esi+4], 'el' + jne @f + cmp byte[esi+6], '=' + jne @f + add esi, 7 + mov edi, kernel +.copy: lodsb + or al, al + jz .copyend + cmp al, ' ' + jz .copyend + cmp al, 13 + jbe .copyend + cmp esi, 0A000h + ja .copyend + cmp edi, loader_end-1 + jae .copyend + stosb + jmp .copy +.copyend: xor al, al + stosb + jmp .getnext +@@: cmp dword[esi], 'nosm' + jne .next + cmp word[esi+4], 'p=' + jne .next + cmp byte[esi+6], '1' + jne .next + mov byte[nosmp], 1 +.next: inc esi + ;failsafe +.getnext: cmp esi, 0A000h + jae .parseend + cmp byte [esi], 0 + je .parseend + ;skip white spaces + cmp byte [esi], ' ' + je @b + cmp byte [esi], 13 + jbe @b + jmp .nextvar +.noconf: mov edi, ebx + mov ecx, 1024 + xor eax, eax + repnz stosd + mov dword [ebx+0], '// N' + mov dword [ebx+4], '/A' or (10 shl 16) +.parseend: + + ;-----load /sys/core------ + mov edx, fsdrivers +.nextfs: xor ebx, ebx + mov bx, word [edx] + or bx, bx + jz .errfs + mov esi, dword [bootboot.initrd_ptr] + mov ecx, dword [bootboot.initrd_size] + add ecx, esi + mov edi, kernel + push edx + call ebx + pop edx + or ecx, ecx + jnz .coreok + add edx, 2 + jmp .nextfs +.errfs2: mov esi, nocore + jmp prot_diefunc +.errfs: ; if all drivers failed, search for the first elf executable + DBG32 dbg_scan + mov esi, dword [bootboot.initrd_ptr] + mov ecx, dword [bootboot.initrd_size] + add ecx, esi + dec esi +@@: inc esi + cmp esi, ecx + jae .errfs2 + cmp dword [esi], 5A2F534Fh ; OS/Z magic + je .alt + cmp dword [esi], 464C457Fh ; ELF magic + je .alt + cmp word [esi], 5A4Dh ; MZ magic + jne @b + mov eax, dword [esi+0x3c] + cmp eax, 65536 + jnl @b + add eax, esi + cmp dword [eax], 00004550h ; PE magic + jne @b + cmp word [eax+4], 8664h ; x86_64 + jne @b + cmp word [eax+20], 20Bh + jne @b +.alt: cmp word [esi+4], 0102h ;lsb 64 bit + jne @b + cmp word [esi+18], 62 ;x86_64 + jne @b + cmp word [esi+0x38], 0 ;e_phnum > 0 + jz @b +.coreok: + ; parse PE + cmp word [esi], 5A4Dh ; MZ magic + jne .tryelf + mov ebx, esi + cmp dword [esi+0x3c], 65536 + jnl .badcore + add esi, dword [esi+0x3c] + cmp dword [esi], 00004550h ; PE magic + jne .badcore + cmp word [esi+4], 8664h ; x86_64 architecture + jne .badcore + cmp word [esi+20], 20Bh ; PE32+ format + jne .badcore + + DBG32 dbg_pe + + mov eax, dword [esi+36] ; entry point + mov dword [entrypoint], eax + mov dword [entrypoint+4], 0FFFFFFFFh + mov ecx, eax + sub ecx, dword [esi+40] ; - code base + add ecx, dword [esi+24] ; + text size + add ecx, dword [esi+28] ; + data size + mov edx, dword [esi+32] ; bss size + shr eax, 31 + jz .badcore + jmp .mkcore + + ; parse ELF +.tryelf: cmp dword [esi], 5A2F534Fh ; OS/Z magic + je @f + cmp dword [esi], 464C457Fh ; ELF magic + jne .badcore +@@: cmp word [esi+4], 0102h ;lsb 64 bit, little endian + jne .badcore + cmp word [esi+18], 62 ;x86_64 architecture + je @f +.badcore: mov esi, badcore + jmp prot_diefunc +@@: + DBG32 dbg_elf + + mov ebx, esi + mov eax, dword [esi+0x18] + mov dword [entrypoint], eax + mov eax, dword [esi+0x18+4] + mov dword [entrypoint+4], eax + ;parse ELF binary + mov cx, word [esi+0x38] ; program header entries phnum + mov eax, dword [esi+0x20] ; program header + add esi, eax + sub esi, 56 + inc cx +.nextph: add esi, 56 + dec cx + jz .badcore + cmp word [esi], 1 ; p_type, loadable + jne .nextph + cmp word [esi+22], 0FFFFh ; p_vaddr == negative address + jne .nextph + ;got it + add ebx, dword [esi+8] ; + P_offset + mov ecx, dword [esi+32] ; p_filesz + ; hack to keep symtab and strtab for shared libraries + cmp byte [ebx+16], 3 + jne @f + add ecx, 04000h +@@: mov edx, dword [esi+40] ; p_memsz + sub edx, ecx + + ;ebx=ptr to core segment, ecx=segment size, edx=bss size +.mkcore: or ecx, ecx + jz .badcore + mov eax, ecx + add eax, edx + cmp eax, 2*1024*1024 - 256*1024 - 2*4096; 2M minus stack for 256 cores minus bootboot and environment + jl @f + mov esi, bigcore + jmp prot_diefunc +@@: mov edi, dword [bootboot.initrd_ptr] + add edi, dword [bootboot.initrd_size] + mov dword [core_ptr], edi + mov dword [core_len], ecx + ;copy code from segment after initrd + mov esi, ebx + mov ebx, ecx + and bl, 3 + shr ecx, 2 + repnz movsd + or bl, bl + jz @f + mov cl, bl + repnz movsb + ;zero out bss +@@: or edx, edx + jz @f + add dword [core_len], edx + xor eax, eax + mov ecx, edx + and dl, 3 + shr ecx, 2 + repnz stosd + or dl, dl + jz @f + mov cl, dl + repnz stosb + ;round up to page size +@@: mov eax, dword [core_len] + add eax, 4095 + shr eax, 12 + shl eax, 12 + mov dword [core_len], eax + + ;exclude initrd area and core from free mmap + mov esi, bootboot.mmap + mov ebx, dword [bootboot.initrd_ptr] + mov edx, dword [core_ptr] + add edx, eax + mov cx, 248 + ; initrd+core (ebx..edx) +.nextfree: cmp dword [esi], ebx + ja .excludeok + mov eax, dword [esi+8] + and al, 0F0h + add eax, dword [esi] + cmp edx, eax + ja .notini + ; +--------------+ + ; ####### + cmp dword [esi], ebx + jne .splitmem + ; ptr = initrd_ptr+initrd_size+core_len + mov dword [esi], edx + sub edx, ebx + and dl, 0F0h + ; size -= initrd_size+core_len + sub dword [esi+8], edx + jmp .excludeok + ; +--+ +----------+ + ; ####### +.splitmem: mov edi, bootboot.magic + add edi, dword [bootboot.size] + add dword [bootboot.size], 16 +@@: mov eax, dword [edi-16] + mov dword [edi], eax + mov eax, dword [edi-12] + mov dword [edi+4], eax + mov eax, dword [edi-8] + mov dword [edi+8], eax + mov eax, dword [edi-4] + mov dword [edi+12], eax + sub edi, 16 + cmp edi, esi + ja @b + mov eax, ebx + sub eax, dword [esi] + sub dword [esi+24], eax + inc al + mov dword [esi+8], eax + mov dword [esi+16], edx + sub edx, ebx + sub dword [esi+24], edx + jmp .excludeok +.notini: add esi, 16 + dec cx + jnz .nextfree +.excludeok: + ; -------- SMP --------- + ; clear LAPIC address and logical core id list + mov edi, lapic_ptr + mov ecx, 512/4+1 + xor eax, eax + repnz stosd + ; clear flags + mov byte [bsp_done], al + + cmp byte [nosmp], al + jne .nosmp + + ; try ACPI first + mov esi, dword [bootboot.acpi_ptr] + or esi, esi + jz .trymp + mov edx, 4 + cmp byte [esi], 'X' ; XSDT has 8 bytes pointers, but we can only access 4G + jne @f + mov edx, 8 +@@: mov ecx, dword [esi+4] + add esi, 024h + sub ecx, 024h +.nextsdt: mov ebx, dword [esi] + add esi, edx + cmp dword [ebx], 'APIC' ; look for MADT + je .madt + sub ecx, edx + or ecx, ecx + jz .trymp + jmp .nextsdt + ; MADT found. +.madt: mov eax, dword [ebx+24h] + mov dword [lapic_ptr], eax ; madt.lapic_address + mov ecx, dword [ebx+4] + sub ecx, 2ch + add ebx, 2ch + mov edi, lapic_ids +.nextmadtentry: + cmp word [bootboot.numcores], 256 + jae .dosmp + cmp byte [ebx], 0 ; madt_entry.type: is it a Local APIC Processor? + jne @f + xor ax, ax + mov al, byte [ebx+2] ; madt_entry.lapicproc.lapicid + stosw ; ACPI table holds 1 byte id, but internally we have 2 bytes + inc word [bootboot.numcores] +@@: xor eax, eax + mov al, byte [ebx+1] ; madt_entry.size + or al, al + jz .dosmp + add ebx, eax + sub ecx, eax + cmp ecx, 0 + jg .nextmadtentry + jmp .dosmp + + +.trymp: ; in lack of ACPI, try legacy MP structures + mov esi, dword [bootboot.mp_ptr] + or esi, esi + jz .nosmp + mov esi, dword [esi+4] + cmp dword [esi], 'PCMP' + jne .nosmp + mov eax, dword [esi+36] ; pcmp.lapic_address + mov dword [lapic_ptr], eax + mov cx, word [esi+34] ; pcmp.numentries + add esi, 44 ; pcmp header length + mov edi, lapic_ids +.nextpcmpentry: + cmp word [bootboot.numcores], 256 + jae .dosmp + cmp byte [esi], 0 ; pcmp_entry.type: is it a Local APIC Processor? + jne @f + xor ax, ax + mov al, byte [esi+1] ; pcmp_entry.lapicproc.lapicid + stosw ; PCMP has 1 byte id, but we use 2 bytes internally + inc word [bootboot.numcores] + add esi, 12 +@@: add esi, 8 + dec cx + jnz .nextpcmpentry + + ; send IPI and SIPI +.dosmp: cmp word [bootboot.numcores], 2 + jb .nosmp + + DBG32 dbg_smp + + ; relocate AP trampoline + mov esi, ap_trampoline + mov edi, 7000h + mov ecx, (ap_start-ap_trampoline+3)/4 + repnz movsd + + ; send Broadcast INIT IPI + mov esi, dword [lapic_ptr] + add esi, 300h + mov eax, 0C4500h + mov dword [esi], eax + + ; wait 10 millisec + prot_realmode + xor cx, cx + mov dx, 10000 + mov ah, 086h + sti + int 15h + cli + real_protmode + + ; send Broadcast STARTUP IPI + mov eax, 0C4607h ; start at 0700:0000h + mov dword [esi], eax + + ; wait 200 microsec + prot_realmode + xor cx, cx + mov dx, 200 + mov ah, 086h + sti + int 15h + cli + real_protmode + + mov eax, 0C4607h ; second SIPI + mov dword [esi], eax + +.nosmp: ; failsafe + cmp word [bootboot.numcores], 0 + jnz @f + inc word [bootboot.numcores] + mov ax, word [bootboot.bspid] + mov word [lapic_ids], ax +@@: ; remove core stacks from memory map + xor eax, eax + mov ax, word [bootboot.numcores] + shl eax, 10 + mov edi, bootboot.mmap + add dword [edi], eax + sub dword [edi+8], eax + + ; ------- set video resolution ------- + prot_realmode + + DBG dbg_vesa + + xor ax, ax + mov es, ax + ;get VESA VBE2.0 info + mov ax, 4f00h + mov di, 0A000h + mov dword [di], 'VBE2' + ;this call requires a big stack + int 10h + cmp ax, 004fh + je @f +.viderr: mov si, novbe + jmp real_diefunc + ;read dword pointer and copy string to 1st 64k + ;available video modes +@@: xor esi, esi + xor edi, edi + mov si, word [0A000h+0Eh] + mov ax, word [0A000h+10h] + mov ds, ax + xor ax, ax + mov es, ax + mov di, 0A000h+400h + mov cx, 64 +@@: lodsw + cmp ax, 0ffffh + je @f + or ax, ax + jz @f + stosw + dec cx + jnz @b +@@: xor ax, ax + stosw + ;iterate on modes + mov si, 0A000h+400h +.nextmode: mov di, 0A000h+800h + xor ax, ax + mov word [0A000h+802h], ax ; vbe mode + lodsw + or ax, ax + jz .viderr + mov cx, ax + mov ax, 4f01h + push bx + push cx + push si + push di + int 10h + pop di + pop si + pop cx + pop bx + cmp ax, 004fh + jne .viderr + bts cx, 13 + bts cx, 14 + mov ax, word [0A000h+800h] ; vbe flags + and ax, VBE_MODEFLAGS + cmp ax, VBE_MODEFLAGS + jne .nextmode + ;check memory model (direct) + cmp byte [0A000h+81bh], 6 + jne .nextmode + ;check bpp + cmp byte [0A000h+819h], 32 + jne .nextmode + ;check min width + mov ax, word [reqwidth] + cmp ax, 640 + ja @f + mov ax, 640 +@@: cmp word [0A000h+812h], ax + jne .nextmode + ;check min height + mov ax, word [reqheight] + cmp ax, 480 + ja @f + mov ax, 480 +@@: cmp word [0A000h+814h], ax + jb .nextmode + ;match? go no further +.match: xor edx, edx + xor ebx, ebx + xor eax, eax + mov bx, word [0A000h+810h] + mov word [bootboot.fb_scanline], bx + mov ax, word [0A000h+812h] + mov word [bootboot.fb_width], ax + mov ax, word [0A000h+814h] + mov word [bootboot.fb_height], ax + mul ebx + mov dword [bootboot.fb_size], eax + mov eax, dword [0A000h+828h] + mov dword [bootboot.fb_ptr], eax + mov byte [bootboot.fb_type],FB_ARGB ; blue offset + cmp byte [0A000h+824h], 0 + je @f + mov byte [bootboot.fb_type],FB_RGBA + cmp byte [0A000h+824h], 8 + je @f + mov byte [bootboot.fb_type],FB_ABGR + cmp byte [0A000h+824h], 16 + je @f + mov byte [bootboot.fb_type],FB_BGRA +@@: ; set video mode + mov bx, cx + bts bx, 14 ;flat linear + mov ax, 4f02h + int 10h + cmp ax, 004fh + jne .viderr + ;no debug output after this point + + ;inform firmware that we're about to leave it's realm + mov ax, 0EC00h + mov bx, 2 ; 64 bit + int 15h + real_protmode + + ; -------- paging --------- + ;map core at higher half of memory + ;address 0xffffffffffe00000 + xor eax, eax + mov edi, 0A000h + mov ecx, (14000h+256*1024-0A000h)/4 + repnz stosd + + ;PML4 + mov edi, 0A000h + ;pointer to 2M PDPE (first 4G RAM identity mapped) + mov dword [edi], 0E003h + ;pointer to 4k PDPE (core mapped at -2M) + mov dword [edi+4096-8], 0B003h + + ;4K PDPE + mov edi, 0B000h + mov dword [edi+4096-8], 0C003h + ;4K PDE + mov edi, 0C000h+3840 + mov eax, dword[bootboot.fb_ptr] ;map framebuffer + mov al,83h + mov ecx, 31 +@@: stosd + add edi, 4 + add eax, 2*1024*1024 + dec ecx + jnz @b + mov dword [0C000h+4096-8], 0D003h + + ;4K PT + mov dword[0D000h], 08001h ;map bootboot + mov dword[0D008h], 09001h ;map configuration + mov edi, 0D010h + mov eax, dword[core_ptr] ;map core text segment + inc eax + mov ecx, dword[core_len] + shr ecx, 12 + inc ecx +@@: stosd + add edi, 4 + add eax, 4096 + dec ecx + jnz @b + ;map core stacks (one page per 4 cores) + mov edi, 0DFF8h + mov eax, 014003h + mov cx, word [bootboot.numcores] + add cx, 3 + shr cx, 2 +@@: mov dword[edi], eax + sub edi, 8 + add eax, 1000h + dec cx + jnz @b + + ;identity mapping + ;2M PDPE + mov edi, 0E000h + mov dword [edi], 0F003h + mov dword [edi+8], 010003h + mov dword [edi+16], 011003h + mov dword [edi+24], 012003h + ;2M PDE + mov edi, 0F000h + xor eax, eax + mov al, 83h + mov ecx, 512* 4;G RAM +@@: stosd + add edi, 4 + add eax, 2*1024*1024 + dec ecx + jnz @b + ;first 2M mapped by page + mov dword [0F000h], 013003h + mov edi, 013000h + mov eax, 3 + mov ecx, 512 +@@: stosd + add edi, 4 + add eax, 4096 + dec ecx + jnz @b + + ;generate new 64 bit gdt + mov edi, GDT_table+8 + ;8h core code + xor eax, eax ;supervisor mode (ring 0) + mov ax, 0FFFFh + stosd + mov eax, 00209800h + stosd + ;10h core data + xor eax, eax ;flat data segment + mov ax, 0FFFFh + stosd + mov eax, 00809200h + stosd + ;18h mandatory tss + xor eax, eax ;required by vt-x + mov al, 068h + stosd + mov eax, 00008900h + stosd + xor eax, eax + stosd + stosd + + ;Enter long mode + cli + mov al, 0FFh ;disable PIC + out 021h, al + out 0A1h, al + in al, 70h ;disable NMI + or al, 80h + out 70h, al + ;release AP spinlock + inc byte [bsp_done] + + ;don't use stack below this line +public longmode_init +longmode_init: + xchg bx, bx + mov eax, 1101101000b ;Set PAE, MCE, PGE; OSFXSR, OSXMMEXCPT (enable SSE) + mov cr4, eax + mov eax, 0A000h + mov cr3, eax + mov ecx, 0C0000080h ;EFER MSR + rdmsr + or eax, 100h ;enable long mode + wrmsr + + mov eax, 0C0000011h ;clear EM, MP (enable SSE) and WP + mov cr0, eax ;enable paging with cache disabled + lgdt [GDT_value] ;read 80 bit address + jmp @f + nop +@@: jmp 8:@f + USE64 +@@: xor eax, eax ;load long mode segments + mov ax, 10h + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + ; find out our lapic id + mov eax, 1 + cpuid + shr ebx, 24 + mov edx, ebx + ; get array index for it + xor rbx, rbx + mov rsi, lapic_ids + mov cx, word [bootboot.numcores] +@@: lodsw + cmp ax, dx + je @f + inc ebx + dec cx + jnz @b + xor rbx, rbx +@@: shl rbx, 10 ; 1k stack for each core + + ; set stack and call _start() in sys/core + xor rsp, rsp ;sp = core_num * -1024 + sub rsp, rbx + xchg bx, bx + jmp qword[entrypoint] + nop + nop + nop + nop + + USE32 + include "fs.inc" + include "tinf.inc" + + ;encryption support for FS/Z +; --- SHA-256 --- +public sha_init +sha_init: xor eax, eax + mov dword [sha_l], eax + mov dword [sha_b], eax + mov dword [sha_b+4], eax + mov dword [sha_s ], 06a09e667h + mov dword [sha_s+ 4], 0bb67ae85h + mov dword [sha_s+ 8], 03c6ef372h + mov dword [sha_s+12], 0a54ff53ah + mov dword [sha_s+16], 0510e527fh + mov dword [sha_s+20], 09b05688ch + mov dword [sha_s+24], 01f83d9abh + mov dword [sha_s+28], 05be0cd19h + ret + + ; IN: ebx = buffer, ecx = length +public sha_upd +sha_upd: push esi + or ecx, ecx + jz .end + mov esi, ebx + mov edi, dword [sha_l] + add edi, sha_d + ; for(;len--;d++) { + ; ctx->d[ctx->l++]=*d; +.next: movsb + inc byte [sha_l] + ; if(ctx->l==64) { + cmp byte [sha_l], 64 + jne @f + ; sha256_t(ctx); + call sha_final.sha_t + ; SHA_ADD(ctx->b[0],ctx->b[1],512); + add dword [sha_b], 512 + adc dword [sha_b+4], 0 + ; ctx->l=0; + mov byte [sha_l], 0 + sub edi, 64 + ; } +@@: dec ecx + jnz .next +.end: pop esi + ret + + ; IN: edi = output buffer +public sha_final +sha_final: push esi + push edi + mov ebx, edi + ; i=ctx->l; ctx->d[i++]=0x80; + mov edi, dword [sha_l] + mov ecx, edi + add edi, sha_d + mov al, 80h + stosb + xor eax, eax + ; if(ctx->l<56) {while(i<56) ctx->d[i++]=0x00;} + cmp cl, 56 + jae @f + neg ecx + add ecx, 63 + xor al, al + repnz stosb + jmp .padded +@@: ; else {while(i<64) ctx->d[i++]=0x00;sha256_t(ctx);memset(ctx->d,0,56);} + stosb + call .sha_t + push ecx + mov ecx, 56/4 + repnz stosd + pop ecx + inc cl + cmp cl, 64 + jne @b +.padded: ; SHA_ADD(ctx->b[0],ctx->b[1],ctx->l*8); + mov eax, dword [sha_l] + shl eax, 3 + add dword [sha_b], eax + adc dword [sha_b+4], 0 + ; ctx->d[63]=ctx->b[0];ctx->d[62]=ctx->b[0]>>8;ctx->d[61]=ctx->b[0]>>16;ctx->d[60]=ctx->b[0]>>24; + mov eax, dword [sha_b] + bswap eax + mov dword [sha_d+60], eax + ; ctx->d[59]=ctx->b[1];ctx->d[58]=ctx->b[1]>>8;ctx->d[57]=ctx->b[1]>>16;ctx->d[56]=ctx->b[1]>>24; + mov eax, dword [sha_b+4] + bswap eax + mov dword [sha_d+56], eax + ; sha256_t(ctx); + call .sha_t + ; for(i=0;i<4;i++) { + ; h[i] =(ctx->s[0]>>(24-i*8)); h[i+4] =(ctx->s[1]>>(24-i*8)); + ; h[i+8] =(ctx->s[2]>>(24-i*8)); h[i+12]=(ctx->s[3]>>(24-i*8)); + ; h[i+16]=(ctx->s[4]>>(24-i*8)); h[i+20]=(ctx->s[5]>>(24-i*8)); + ; h[i+24]=(ctx->s[6]>>(24-i*8)); h[i+28]=(ctx->s[7]>>(24-i*8)); + ; } + mov edi, ebx + mov esi, sha_s + mov cl, 8 +@@: lodsd + bswap eax + stosd + dec cl + jnz @b + pop edi + pop esi + ret +; private func, sha transform +.sha_t: push esi + push edi + push edx + push ecx + push ebx + ; for(i=0,j=0;i<16;i++,j+=4) m[i]=(ctx->d[j]<<24)|(ctx->d[j+1]<<16)|(ctx->d[j+2]<<8)|(ctx->d[j+3]); + mov cl, 16 + mov edi, _m + mov esi, sha_d +@@: lodsd + bswap eax + stosd + dec cl + jnz @b + ; for(;i<64;i++) m[i]=SHA_SIG1(m[i-2])+m[i-7]+SHA_SIG0(m[i-15])+m[i-16]; + mov cl, 48 + ; SHA_SIG0[m[i-15]) (SHA_ROTR(x,7)^SHA_ROTR(x,18)^((x)>>3)) +@@: mov eax, dword [edi-15*4] + mov ebx, eax + mov edx, eax + ror eax, 7 + ror ebx, 18 + shr edx, 3 + xor eax, ebx + xor eax, edx + ; SHA_SIG1(m[i-2]) (SHA_ROTR(x,17)^SHA_ROTR(x,19)^((x)>>10)) + mov ebx, dword [edi-2*4] + mov edx, ebx + ror ebx, 17 + ror edx, 19 + xor ebx, edx + rol edx, 19 + shr edx, 10 + xor ebx, edx + add eax, ebx + ; m[i-7] + add eax, dword [edi-7*4] + ; m[i-16] + add eax, dword [edi-16*4] + stosd + dec cl + jnz @b + ; a=ctx->s[0];b=ctx->s[1];c=ctx->s[2];d=ctx->s[3]; + ; e=ctx->s[4];f=ctx->s[5];g=ctx->s[6];h=ctx->s[7]; + xor ecx, ecx + mov cl, 8 + mov esi, sha_s + mov edi, _a + repnz movsd + ; for(i=0;i<64;i++) { + mov esi, _m +@@: ; t1=h+SHA_EP1(e)+SHA_CH(e,f,g)+sha256_k[i]+m[i]; + mov eax, dword [_h] + mov dword [t1], eax + ; SHA_EP1(e) (SHA_ROTR(x,6)^SHA_ROTR(x,11)^SHA_ROTR(x,25)) + mov eax, dword [_e] + mov ebx, eax + ror eax, 6 + ror ebx, 11 + xor eax, ebx + ror ebx, 14 ; 25 = 11+14 + xor eax, ebx + add dword [t1], eax + ; SHA_CH(e,f,g) (((x)&(y))^(~(x)&(z))) + mov eax, dword [_e] + mov ebx, eax + not ebx + and eax, dword [_f] + and ebx, dword [_g] + xor eax, ebx + add dword [t1], eax + ; sha256_k[i] + mov eax, dword [sha256_k+4*ecx] + add dword [t1], eax + ; m[i] + lodsd + add dword [t1], eax + ; t2=SHA_EP0(a)+SHA_MAJ(a,b,c); + ; SHA_EP0(a) (SHA_ROTR(x,2)^SHA_ROTR(x,13)^SHA_ROTR(x,22)) + mov eax, dword [_a] + mov ebx, eax + ror eax, 2 + ror ebx, 13 + xor eax, ebx + ror ebx, 9 ; 22 = 13+9 + xor eax, ebx + mov dword [t2], eax + ; SHA_MAJ(a,b,c) (((x)&(y))^((x)&(z))^((y)&(z))) + mov eax, dword [_a] + mov edx, dword [_c] + mov ebx, eax + and eax, dword [_b] + and ebx, edx + xor eax, ebx + mov ebx, dword [_b] + and ebx, edx + xor eax, ebx + add dword [t2], eax + ; h=g;g=f;f=e;e=d+t1;d=c;c=b;b=a;a=t1+t2; + mov eax, dword [_g] + mov dword [_h], eax + mov eax, dword [_f] + mov dword [_g], eax + mov eax, dword [_e] + mov dword [_f], eax + mov eax, dword [_d] + add eax, dword [t1] + mov dword [_e], eax + mov eax, dword [_c] + mov dword [_d], eax + mov eax, dword [_b] + mov dword [_c], eax + mov eax, dword [_a] + mov dword [_b], eax + mov eax, dword [t1] + add eax, dword [t2] + mov dword [_a], eax + ; } + inc cl + cmp cl, 64 + jne @b + ; ctx->s[0]+=a;ctx->s[1]+=b;ctx->s[2]+=c;ctx->s[3]+=d; + ; ctx->s[4]+=e;ctx->s[5]+=f;ctx->s[6]+=g;ctx->s[7]+=h; + mov cl, 8 + mov esi, _a + mov edi, sha_s +@@: lodsd + add dword [edi], eax + add edi, 4 + dec cl + jnz @b + pop ebx + pop ecx + pop edx + pop edi + pop esi + xor eax, eax + ret + +; --- CRC-32c --- + ; IN: esi = buffer, ecx = length + ; OUT: edx = crc +public crc32_calc +crc32_calc: xor edx, edx + xor eax, eax + xor ebx, ebx + or cx, cx + jz .end +.next: lodsb + mov bl, dl + xor bl, al + mov eax, edx + shr edx, 8 + xor edx, dword [crclkp+4*ebx] + dec cx + jnz .next +.end: ret + +;********************************************************************* +;* Data * +;********************************************************************* + ;global descriptor table + align 16 +public GDT_table +GDT_table: dd 0, 0 ;null descriptor +DATA_PROT = $-GDT_table + dd 0000FFFFh,008F9200h ;flat ds +DATA_BOOT = $-GDT_table + dd 0000FFFFh,00009200h ;16 bit legacy real mode ds +CODE_BOOT = $-GDT_table + dd 0000FFFFh,00009800h ;16 bit legacy real mode cs +CODE_PROT = $-GDT_table + dd 0000FFFFh,00CF9A00h ;32 bit prot mode ring0 cs + dd 00000068h,00CF8900h ;32 bit TSS, not used but required +public GDT_value +GDT_value: dw $-GDT_table + dd GDT_table + dd 0,0 + ;lookup tables for initrd encryption + dw 0 +public crclkp +crclkp: dd 000000000h, 0F26B8303h, 0E13B70F7h, 01350F3F4h, 0C79A971Fh, 035F1141Ch, 026A1E7E8h, 0D4CA64EBh + dd 08AD958CFh, 078B2DBCCh, 06BE22838h, 09989AB3Bh, 04D43CFD0h, 0BF284CD3h, 0AC78BF27h, 05E133C24h + dd 0105EC76Fh, 0E235446Ch, 0F165B798h, 0030E349Bh, 0D7C45070h, 025AFD373h, 036FF2087h, 0C494A384h + dd 09A879FA0h, 068EC1CA3h, 07BBCEF57h, 089D76C54h, 05D1D08BFh, 0AF768BBCh, 0BC267848h, 04E4DFB4Bh + dd 020BD8EDEh, 0D2D60DDDh, 0C186FE29h, 033ED7D2Ah, 0E72719C1h, 0154C9AC2h, 0061C6936h, 0F477EA35h + dd 0AA64D611h, 0580F5512h, 04B5FA6E6h, 0B93425E5h, 06DFE410Eh, 09F95C20Dh, 08CC531F9h, 07EAEB2FAh + dd 030E349B1h, 0C288CAB2h, 0D1D83946h, 023B3BA45h, 0F779DEAEh, 005125DADh, 01642AE59h, 0E4292D5Ah + dd 0BA3A117Eh, 04851927Dh, 05B016189h, 0A96AE28Ah, 07DA08661h, 08FCB0562h, 09C9BF696h, 06EF07595h + dd 0417B1DBCh, 0B3109EBFh, 0A0406D4Bh, 0522BEE48h, 086E18AA3h, 0748A09A0h, 067DAFA54h, 095B17957h + dd 0CBA24573h, 039C9C670h, 02A993584h, 0D8F2B687h, 00C38D26Ch, 0FE53516Fh, 0ED03A29Bh, 01F682198h + dd 05125DAD3h, 0A34E59D0h, 0B01EAA24h, 042752927h, 096BF4DCCh, 064D4CECFh, 077843D3Bh, 085EFBE38h + dd 0DBFC821Ch, 02997011Fh, 03AC7F2EBh, 0C8AC71E8h, 01C661503h, 0EE0D9600h, 0FD5D65F4h, 00F36E6F7h + dd 061C69362h, 093AD1061h, 080FDE395h, 072966096h, 0A65C047Dh, 05437877Eh, 04767748Ah, 0B50CF789h + dd 0EB1FCBADh, 0197448AEh, 00A24BB5Ah, 0F84F3859h, 02C855CB2h, 0DEEEDFB1h, 0CDBE2C45h, 03FD5AF46h + dd 07198540Dh, 083F3D70Eh, 090A324FAh, 062C8A7F9h, 0B602C312h, 044694011h, 05739B3E5h, 0A55230E6h + dd 0FB410CC2h, 0092A8FC1h, 01A7A7C35h, 0E811FF36h, 03CDB9BDDh, 0CEB018DEh, 0DDE0EB2Ah, 02F8B6829h + dd 082F63B78h, 0709DB87Bh, 063CD4B8Fh, 091A6C88Ch, 0456CAC67h, 0B7072F64h, 0A457DC90h, 0563C5F93h + dd 0082F63B7h, 0FA44E0B4h, 0E9141340h, 01B7F9043h, 0CFB5F4A8h, 03DDE77ABh, 02E8E845Fh, 0DCE5075Ch + dd 092A8FC17h, 060C37F14h, 073938CE0h, 081F80FE3h, 055326B08h, 0A759E80Bh, 0B4091BFFh, 0466298FCh + dd 01871A4D8h, 0EA1A27DBh, 0F94AD42Fh, 00B21572Ch, 0DFEB33C7h, 02D80B0C4h, 03ED04330h, 0CCBBC033h + dd 0A24BB5A6h, 0502036A5h, 04370C551h, 0B11B4652h, 065D122B9h, 097BAA1BAh, 084EA524Eh, 07681D14Dh + dd 02892ED69h, 0DAF96E6Ah, 0C9A99D9Eh, 03BC21E9Dh, 0EF087A76h, 01D63F975h, 00E330A81h, 0FC588982h + dd 0B21572C9h, 0407EF1CAh, 0532E023Eh, 0A145813Dh, 0758FE5D6h, 087E466D5h, 094B49521h, 066DF1622h + dd 038CC2A06h, 0CAA7A905h, 0D9F75AF1h, 02B9CD9F2h, 0FF56BD19h, 00D3D3E1Ah, 01E6DCDEEh, 0EC064EEDh + dd 0C38D26C4h, 031E6A5C7h, 022B65633h, 0D0DDD530h, 00417B1DBh, 0F67C32D8h, 0E52CC12Ch, 01747422Fh + dd 049547E0Bh, 0BB3FFD08h, 0A86F0EFCh, 05A048DFFh, 08ECEE914h, 07CA56A17h, 06FF599E3h, 09D9E1AE0h + dd 0D3D3E1ABh, 021B862A8h, 032E8915Ch, 0C083125Fh, 0144976B4h, 0E622F5B7h, 0F5720643h, 007198540h + dd 0590AB964h, 0AB613A67h, 0B831C993h, 04A5A4A90h, 09E902E7Bh, 06CFBAD78h, 07FAB5E8Ch, 08DC0DD8Fh + dd 0E330A81Ah, 0115B2B19h, 0020BD8EDh, 0F0605BEEh, 024AA3F05h, 0D6C1BC06h, 0C5914FF2h, 037FACCF1h + dd 069E9F0D5h, 09B8273D6h, 088D28022h, 07AB90321h, 0AE7367CAh, 05C18E4C9h, 04F48173Dh, 0BD23943Eh + dd 0F36E6F75h, 00105EC76h, 012551F82h, 0E03E9C81h, 034F4F86Ah, 0C69F7B69h, 0D5CF889Dh, 027A40B9Eh + dd 079B737BAh, 08BDCB4B9h, 0988C474Dh, 06AE7C44Eh, 0BE2DA0A5h, 04C4623A6h, 05F16D052h, 0AD7D5351h + +public sha256_k +sha256_k: dd 0428a2f98h, 071374491h, 0b5c0fbcfh, 0e9b5dba5h, 03956c25bh, 059f111f1h, 0923f82a4h, 0ab1c5ed5h + dd 0d807aa98h, 012835b01h, 0243185beh, 0550c7dc3h, 072be5d74h, 080deb1feh, 09bdc06a7h, 0c19bf174h + dd 0e49b69c1h, 0efbe4786h, 00fc19dc6h, 0240ca1cch, 02de92c6fh, 04a7484aah, 05cb0a9dch, 076f988dah + dd 0983e5152h, 0a831c66dh, 0b00327c8h, 0bf597fc7h, 0c6e00bf3h, 0d5a79147h, 006ca6351h, 014292967h + dd 027b70a85h, 02e1b2138h, 04d2c6dfch, 053380d13h, 0650a7354h, 0766a0abbh, 081c2c92eh, 092722c85h + dd 0a2bfe8a1h, 0a81a664bh, 0c24b8b70h, 0c76c51a3h, 0d192e819h, 0d6990624h, 0f40e3585h, 0106aa070h + dd 019a4c116h, 01e376c08h, 02748774ch, 034b0bcb5h, 0391c0cb3h, 04ed8aa4ah, 05b9cca4fh, 0682e6ff3h + dd 0748f82eeh, 078a5636fh, 084c87814h, 08cc70208h, 090befffah, 0a4506cebh, 0bef9a3f7h, 0c67178f2h +public idt16 +idt16: dw 3FFh + dq 0 +public lbapacket +lbapacket: ;lba packet for BIOS +.size: dw 10h +.count: dw 8 +.addr: dd 0A000h +.sect0: dd 0 +.sect1: dd 0 +public spc_packet +spc_packet: db 18h dup 0 +public reqwidth +reqwidth: dd 1024 +public reqheight +reqheight: dd 768 +public ebdaptr +ebdaptr: dd 0 +public hw_stack +hw_stack: dd 0 +public bpb_sec +bpb_sec: dd 0 ;ESP's first sector +public root_sec +root_sec: dd 0 ;root directory's first sector +public data_sec +data_sec: dd 0 ;first data sector +public clu_sec +clu_sec: dd 0 ;sector per cluster +public origcount +origcount: dw 0 +public bootdev +bootdev: db 0 +public readdev +readdev: db 0 +public cntdev +cntdev: db 0 +public hasinitrd +hasinitrd: db 0 +public hasconfig +hasconfig: db 0 +public iscdrom +iscdrom: db 0 +public nosmp +nosmp: db 0 +public bsp_done +bsp_done: ;flag to indicate APs can run +public fattype +fattype: db 0 +public bkp +bkp: dd ' ' +if BBDEBUG eq 1 +dbg_cpu db " * Detecting CPU",10,13,0 +dbg_A20 db " * Enabling A20",10,13,0 +dbg_mem db " * E820 Memory Map",10,13,0 +dbg_systab db " * System tables",10,13,0 +dbg_time db " * System time",10,13,0 +dbg_serial db " * Initrd over serial",10,13,0 +dbg_gpt db " * Reading GPT",10,13,0 +dbg_cdrom db " * Detected CDROM boot",10,13,0 +dbg_env db " * Environment",10,13,0 +dbg_initrd db " * Initrd loaded",10,13,0 +dbg_gzinitrd db " * Gzip compressed initrd",10,13,0 +dbg_scan db " * Autodetecting kernel",10,13,0 +dbg_elf db " * Parsing ELF64",10,13,0 +dbg_pe db " * Parsing PE32+",10,13,0 +dbg_smp db " * SMP init",10,13,0 +dbg_vesa db " * Screen VESA VBE",10,13,0 +end if +backup: db " * Backup initrd",10,13,0 +passphrase: db " * Passphrase? ",0 +decrypting: db 13," * Decrypting...",0 +clrdecrypt: db 13," ",13,0 +public starting +starting: db "Booting OS..." +crlf: db 10,13,0 +public panic +panic: db "-PANIC: ",0 +public noarch +noarch: db "Hardware not supported",0 +public a20err +a20err: db "Failed to enable A20",0 +public memerr +memerr: db "E820 memory map not found",0 +public nogzmem +nogzmem: db "Inflating: " +public noenmem +noenmem: db "Not enough memory",0 +public noacpi +noacpi: db "ACPI not found",0 +public nogpt +nogpt: db "No GPT found",0 +public nopar +nopar: db "No boot partition",0 +public nord +nord: db "Initrd not found",0 +public nolib +nolib: db "/sys not found in initrd",0 +public nocore +nocore: db "Kernel not found in initrd",0 +public badcore +badcore: db "Kernel is not a valid executable",0 +public bigcore +bigcore: db "Kernel is too big",0 +public novbe +novbe: db "VESA VBE error, no framebuffer",0 +public nogzip +nogzip: db "Unable to uncompress",0 +public notcdsect +notcdsect: db "Not 2048 sector aligned",0 +public nocipher +nocipher: db "Unsupported cipher",10,13,0 +public badpass +badpass: db 13,"BOOTBOOT-ERROR: Bad passphrase",10,13,0 +public cfgfile +cfgfile: db "sys/config",0,0,0 +public kernel +kernel: db "sys/core" + db (64-($-kernel)) dup 0 +;-----------padding to be multiple of 512---------- + db (511-($-loader+511) mod 512) dup 0 +public loader_end +loader_end: + +;-----------BIOS checksum------------ +chksum = 0 +repeat $-loader + load b byte from (loader+%-1) + chksum = (chksum + b) mod 100h +end repeat +store byte (100h-chksum) at (loader.checksum) + +;-----------bss area----------- +entrypoint: dq ? +core_ptr: dd ? +core_len: dd ? +gpt_ptr: dd ? +gpt_num: dd ? +gpt_ent: dd ? +lapic_ptr: dd ? +lapic_ids: +tinf_bss_start: +d_end: dd ? +d_lzOff: dd ? +d_dict_ring:dd ? +d_dict_size:dd ? +d_dict_idx: dd ? +d_tag: db ? +d_bitcount: db ? +d_bfinal: db ? +d_curlen: dw ? +d_ltree: +d_ltree_table: + dw 16 dup ? +d_ltree_trans: + dw 288 dup ? +d_dtree: +d_dtree_table: + dw 16 dup ? +d_dtree_trans: + dw 288 dup ? +offs: dw 16 dup ? +num: dw ? +lengths: db 320 dup ? +hlit: dw ? +hdist: dw ? +hclen: dw ? +tinf_bss_end: + +virtual at tinf_bss_start +pass: db 256 dup ? +sha_d: db 64 dup ? +sha_l: dd ? +sha_b: dd 2 dup ? +sha_s: dd 8 dup ? +_a: dd ? +_b: dd ? +_c: dd ? +_d: dd ? +_e: dd ? +_f: dd ? +_g: dd ? +_h: dd ? +t1: dd ? +t2: dd ? +_m: dd 64 dup ? +chk: db 32 dup ? +iv: db 32 dup ? +pl: dd ? +_i: dd ? +end virtual + +;-----------bound check------------- +;fasm will generate an error if the code +;is bigger than it should be +db 07C00h-4096-($-loader) dup ? diff --git a/bootboot.inc b/bootboot.inc new file mode 100644 index 0000000..3dc7b6a --- /dev/null +++ b/bootboot.inc @@ -0,0 +1,125 @@ +;* +;* x86_64-bios/bootboot.inc +;* +;* Copyright (C) 2017 - 2020 bzt (bztsrc@gitlab) +;* +;* Permission is hereby granted, free of charge, to any person +;* obtaining a copy of this software and associated documentation +;* files (the "Software"), to deal in the Software without +;* restriction, including without limitation the rights to use, copy, +;* modify, merge, publish, distribute, sublicense, and/or sell copies +;* of the Software, and to permit persons to whom the Software is +;* furnished to do so, subject to the following conditions: +;* +;* The above copyright notice and this permission notice shall be +;* included in all copies or substantial portions of the Software. +;* +;* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +;* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +;* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;* DEALINGS IN THE SOFTWARE. +;* +;* This file is part of the BOOTBOOT Protocol package. +;* @brief The BOOTBOOT structure +;* +; ------ !!! WARNING: MUST MATCH ../bootboot.h !!! ------ + +bootboot = 8000h + + + + + +; this define is in the 18th line of bootboot.h +bootboot_MAGIC equ 'BOOT' + + + + + + + + +; minimum protocol level: +; hardcoded kernel name, static kernel memory addresses +PROTOCOL_MINIMAL equ 0 +; static protocol level: +; kernel name parsed from environment, static kernel memory addresses +PROTOCOL_STATIC equ 1 +; dynamic protocol level: +; kernel name parsed from environment, kernel memory addresses parsed from ELF symbols +PROTOCOL_DYNAMIC equ 2 +; big-endian flag +PROTOCOL_BIGENDIAN equ 080h + +; loader types, just informational +LOADER_BIOS equ 0 +LOADER_UEFI equ 4 +LOADER_RPI equ 8 +LOADER_COREBOOT equ 16 + +; framebuffer pixel format, only 32 bits supported +FB_ARGB equ 0 +FB_RGBA equ 1 +FB_ABGR equ 2 +FB_BGRA equ 3 + + + +; mmap entry, type is stored in least significant tetrad of size +virtual at 0 + mmap_ent.ptr: dq 0 + mmap_ent.size: dq 0 +end virtual +; we don't have entry field macros for asm +; realsize = size & 0xFFFFFFFFFFF0 +; type = size & 0xF + + +MMAP_USED equ 0 +MMAP_FREE equ 1 +MMAP_ACPI equ 2 +MMAP_MMIO equ 3 + +INITRD_MAXSIZE equ 16 ; Mb + +virtual at bootboot + ; first 64 bytes is platform independent + bootboot.magic: dd 0 + bootboot.size: dd 0 + bootboot.protocol: db 1 + bootboot.fb_type: db 0 + bootboot.numcores: dw 0 + bootboot.bspid: dw 0 + bootboot.timezone: dw 0 + bootboot.datetime: dq 0 + bootboot.initrd_ptr: dq 0 + bootboot.initrd_size: dq 0 + bootboot.fb_ptr: dq 0 + bootboot.fb_size: dd 0 + bootboot.fb_width: dd 0 + bootboot.fb_height: dd 0 + bootboot.fb_scanline: dd 0 + + ; the rest (64 bytes) is platform specific + + ; x86_64 + bootboot.acpi_ptr: dq 0 + bootboot.smbi_ptr: dq 0 + bootboot.efi_ptr: dq 0 + bootboot.mp_ptr: dq 0 + bootboot.unused: dq 0,0,0,0 + + bootboot.mmap: +end virtual + + + + + + + diff --git a/disk-x86.img.gz b/disk-x86.img.gz new file mode 100644 index 0000000000000000000000000000000000000000..05ce581e4c6cea9f427935ac42cb2a7b3f44331b GIT binary patch literal 188724 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≦%JQj@^!T+CTUkpvK`l*Ljz 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≫zf)6!J4coVV`=G)+|g$oS1 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 literal 0 HcmV?d00001 diff --git a/fs.inc b/fs.inc new file mode 100644 index 0000000..a132eaf --- /dev/null +++ b/fs.inc @@ -0,0 +1,656 @@ +;* +;* x86_64-bios/fs.inc +;* +;* Copyright (C) 2017 - 2020 bzt (bztsrc@gitlab) +;* +;* Permission is hereby granted, free of charge, to any person +;* obtaining a copy of this software and associated documentation +;* files (the "Software"), to deal in the Software without +;* restriction, including without limitation the rights to use, copy, +;* modify, merge, publish, distribute, sublicense, and/or sell copies +;* of the Software, and to permit persons to whom the Software is +;* furnished to do so, subject to the following conditions: +;* +;* The above copyright notice and this permission notice shall be +;* included in all copies or substantial portions of the Software. +;* +;* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +;* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +;* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;* DEALINGS IN THE SOFTWARE. +;* +;* This file is part of the BOOTBOOT Protocol package. +;* @brief Filesystem drivers for initial ramdisk. +;* + +;********************************************************************* +;* File System Drivers * +;********************************************************************* + + USE32 +fsdrivers: + dw fsz_initrd + dw cpio_initrd + dw tar_initrd + dw sfs_initrd + dw jamesm_initrd + dw 0 + +; ----------- FS/Z ---------- +; Find the kernel on initrd (only supports 4096 logical sector sizes) +; IN: esi: initrd pointer, ecx: initrd end, edi: kernel filename +; OUT: On Success +; esi: pointer to the first byte, ecx: size in bytes +; On Error +; ecx: 0 +fsz_initrd: + mov ebx, ecx + xor ecx, ecx + ; FS/Z superblock + cmp dword [esi+512], 'FS/Z' ; FSZ_SuperBlock.magic + jne .nolib + ; encrypted initrd? + cmp dword [esi+708], 0 ; FSZ_SuperBlock.enchash + jz .noenc + mov al, byte [esi+518] ; FSZ_SuperBlock.flags + shr al, 2 + and al, 7 + or al, al + jz @f + prot_realmode + real_print loader.name + real_print panic + mov esi, nocipher + call real_printfunc + real_protmode + jmp .err +@@: push edi + prot_realmode +.passagain: real_print passphrase + ; get passphrase from user + mov di, pass + mov byte [di], 0 +.getchar: call real_getchar + cmp al, 27 ; Esc + jne @f + real_print clrdecrypt + jmp .err +@@: cmp al, 8 ; Backspace + jne @f + cmp di, pass + je .getchar + mov byte [di], 0 + dec di + jmp .getchar +@@: cmp al, 13 ; Enter + je .gotpass + cmp al, 10 + je .gotpass + cmp al, ' ' + jb .getchar + cmp di, pass+255 + jge .getchar + mov word [di], ax + inc di + jmp .getchar +.gotpass: push esi + real_protmode + mov esi, pass + mov ecx, edi + sub ecx, esi + mov dword [pl], ecx + call crc32_calc + prot_realmode + pop esi + cmp dword [esi+708], edx + je .passok + real_print badpass + jmp .passagain +.passok: real_print decrypting + real_protmode + ; decrypt initrd + call sha_init + mov ecx, dword [pl] + mov ebx, pass + call sha_upd + mov ecx, 6 + mov ebx, esi + add ebx, 512 ; FSZ_SuperBlock.magic + call sha_upd + mov edi, chk + call sha_final + mov edi, esi + add edi, 680 ; FSZ_SuperBlock.encrypt + mov cl, 28 + xor ebx, ebx +@@: mov al, byte [edi] + xor byte [chk+ebx], al + xor eax, eax + stosb + inc ebx + dec cl + jnz @b + stosd + call sha_init + mov ecx, 28 + mov ebx, chk + call sha_upd + mov edi, iv + call sha_final + mov eax, dword [esi+528] ; FSZ_SuperBlock.numsec + mov dword [pl], eax + xor eax, eax + inc eax + mov dword [_i], eax ; skip first sector + mov ebx, esi + add ebx, 4096 + push esi +.decrypt: mov esi, iv + mov edi, chk + xor ecx, ecx + mov cl, 32/4 + repnz movsd + mov cx, 4096 +.nextblk: mov al, bl + and al, 31 + jnz @f + push ebx + push ecx + call sha_init + mov ecx, 32 + mov ebx, chk + call sha_upd + mov ecx, 4 + mov ebx, _i + call sha_upd + mov edi, chk + call sha_final + pop ecx + pop ebx + mov edx, edi +@@: mov al, byte [edx] + xor byte [ebx], al + mov al, byte [edx+32] + xor byte [ebx], al + inc ebx + inc edx + dec cx + jnz .nextblk + inc dword [_i] + mov eax, dword [_i] + cmp eax, dword [pl] + jne .decrypt + mov esi, dword [esp] + add esi, 512 + mov ecx, 508 + call crc32_calc + pop esi + mov dword [esi+1020], edx ; FSZ_SuperBlock.chksum + ; clear console message + prot_realmode + real_print clrdecrypt + real_protmode + pop edi + + ; get root dir inode +.noenc: mov dword [_i], 1024 + mov al, byte [esi+518] ; FSZ_SuperBlock.flags + bt ax, 0 ; FSZ_FLAG_BIGINODE? + jnc @f + mov dword [_i], 2048 +@@: mov eax, dword [esi+560] ; FSZ_SuperBlock.rootdirfid + shl eax, 12 + add esi, eax + cmp dword [esi], 'FSIN' + je @f +.nolib: mov esi, nolib +.err: xor ecx, ecx + ret +.nocore: mov esi, nocore + jmp .err +@@: ; it has inlined data? +.again: mov eax, dword [esi+448] ; FSZ_Inode.sec + add esi, dword[_i] ; FSZ_Inode.[big|small].inlinedata + cmp dword [esi], 'FSDR' + je .srchdir + ; no, locate the data + mov ecx, dword [esi] + shl eax, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, eax + cmp dword [esi], 'FSDR' + je .srchdir + ; inlined sector directory or list? + shl ecx, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, ecx + cmp dword [esi], 'FSDR' + jne .nolib +.srchdir: ; find sys/ + mov ecx, dword [esi+16] ; FSZ_DirEntHeader.numentries + mov eax, dword [edi] +@@: add esi, 128 ; directories than + cmp dword [esi+17], eax + je @f + dec ecx + jnz @b + jmp .nolib + ; found, get it's inode +@@: + mov eax, dword [esi] + shl eax, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, eax + cmp dword [esi], 'FSIN' + jne .nolib + + ;this is not bullet proof + add edi, 4 + cmp byte [edi+3], '/' + je .again + + ; it has inlined data? + mov eax, dword [esi+448] ; FSZ_Inode.sec + add esi, dword[_i] ; FSZ_Inode.[big|small].inlinedata + cmp dword [esi], 'FSDR' + je .srchcore + ; no, locate the data + mov ecx, dword [esi] + shl eax, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, eax + cmp dword [esi], 'FSDR' + je .srchdir + ; inlined sector directory or list? + shl ecx, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, ecx + cmp dword [esi], 'FSDR' + jne .nolib + +.srchcore: ; find filename + mov ecx, dword [esi+16] ; FSZ_DirEntHeader.numentries + ;filename, 8 characters supported + mov eax, dword [edi] + mov edx, dword [edi+4] +@@: add esi, 128 + cmp dword [esi+21], edx + jne .not + cmp dword [esi+17], eax + je @f +.not: dec ecx + jnz @b + jmp .nocore + ; found, get it's inode +@@: mov eax, dword [esi] + shl eax, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, eax + cmp dword [esi], 'FSIN' + jne .nocore + ; get data + mov eax, dword [esi+448] ; FSZ_Inode.sec + mov ecx, dword [esi+464] ; FSZ_Inode.size + mov bl, byte [esi+488] ; FSZ_Inode.flags + + ; inline + cmp bl, 0FFh ; FSZ_IN_FLAG_INLINE + jne @f + add esi, dword[_i] ; FSZ_Inode.[big|small].inlinedata + ret + ; direct data block +@@: or bl, bl ; FSZ_IN_FLAG_DIRECT + je .load + ; inlined sector directory or sector list +@@: cmp bl, 07Fh ; FSZ_IN_FLAG_SDINLINE + je @f + cmp bl, 080h ; FSZ_IN_FLAG_SECLIST + je @f + cmp bl, 1 ; FSZ_IN_FLAG_SD + jne .nocore + shl eax, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, eax + mov eax, dword [esi] ; first FSZ_SectorList.sec + jmp .load +@@: add esi, dword[_i] ; FSZ_Inode.[big|small].inlinedata + ; sector directory at esi, file size in ecx + mov eax, dword [esi] ; first FSZ_SectorList.sec +.load: shl eax, 12 + mov esi, dword [bootboot.initrd_ptr] + add esi, eax + ret + +; ----------- cpio ---------- +; Find the kernel on initrd +; IN: esi: initrd pointer, ecx: initrd end, edi: kernel filename +; OUT: On Success +; esi: pointer to the first byte, ecx: size in bytes +; On Error +; ecx: 0 +cpio_initrd: + ; upper bound + mov ebx, ecx + xor ecx, ecx + ; strlen(kernel) + mov eax, edi + or eax, eax + jz .err + cmp byte [eax], 0 + jz .err + xor ecx, ecx +@@: inc ecx + inc eax + cmp byte [eax], 0 + jnz @b + mov dword [.ks], ecx + ; while(ptr.magic=='070707' && ptrsymbol translation table (symbols sorted by code) + xor eax, eax ;i +@@: cmp byte [ebx], 0 + jz .null + xor edx, edx + mov dl, byte [ebx] ;lengths[i] + inc word [offs+2*edx] + mov dx, word [offs+2*edx] + dec dx + mov word [ebp+2*edx+16], ax +.null: inc ebx + inc eax + dec cx + jnz @b + + pop edi + ret + +tinf_decode_trees: + mov word [num], 0 + + ; get 5 bits HLIT (257-286) + xor ecx, ecx + mov cl, 5 + mov ebx, 257 + call tinf_read_bits + mov word [hlit], ax + mov word [num], ax + + ; get 5 bits HDIST (1-32) + mov cl, 5 + xor ebx, ebx + inc ebx + call tinf_read_bits + mov word [hdist], ax + add word [num], ax + + ; get 4 bits HCLEN (4-19) + mov cl, 4 + mov ebx, ecx + call tinf_read_bits + mov word [hclen], ax + + push edi + mov cl, 19 + mov edi, lengths + xor ax, ax + repnz stosw + pop edi + + ; read code lengths for code length alphabet + mov edx, clcidx + ; get 3 bits code length (0-7) +@@: mov cx, 3 + xor ebx, ebx + call tinf_read_bits + xor ebx, ebx + mov bl, byte [edx] ;clcidx[i] + mov byte[ebx+lengths], al + inc edx + dec word [hclen] + jnz @b + + ; build code length tree, temporarily use length tree + mov ebp, d_ltree + mov ebx, lengths + xor ecx, ecx + mov cl, 19 + call tinf_build_tree + + ; decode code lengths for the dynamic trees + mov edx, lengths +.decode: mov ebp, d_ltree + call tinf_decode_symbol + xor ebx, ebx + cmp al, 16 + jne .not16 + ; copy previous code length 3-6 times (read 2 bits) + mov cl, 2 + mov bl, 3 + call tinf_read_bits + mov cx, ax + mov al, byte [edx-1] ;lengths[num-1] +@@: mov byte [edx], al + inc edx + dec word [num] + dec cl + jnz @b + jmp .next + +.not16: cmp al, 17 + jne .not17 + ; repeat code length 0 for 3-10 times (read 3 bits) + mov cl, 3 + mov bl, cl + call tinf_read_bits + mov cx, ax +@@: mov byte [edx], 0 + inc edx + dec word [num] + dec cl + jnz @b + jmp .next + +.not17: cmp al, 18 + jne .not18 + ; repeat code length 0 for 11-138 times (read 7 bits) + mov cl, 7 + mov bl, 11 + call tinf_read_bits + mov cx, ax +@@: mov byte [edx], 0 + inc edx + dec word [num] + dec cl + jnz @b + jmp .next + +.not18: ; values 0-15 represent the actual code lengths + mov byte [edx], al + inc edx + dec word [num] + +.next: cmp word [num], 0 + jnz .decode + + ; build dynamic trees + mov ebp, d_ltree + mov ebx, lengths + xor ecx, ecx + mov cx, word [hlit] + call tinf_build_tree + + mov ebp, d_dtree + mov ebx, lengths + xor ecx, ecx + mov cx, word [hlit] + add ebx, ecx + mov cx, word [hdist] + call tinf_build_tree + ret + +;OUT: +; al: status +tinf_inflate_block_data: + cmp word [d_curlen], 0 + jne .notnull + mov ebp, d_ltree + call tinf_decode_symbol + ; literal byte + cmp ax, 256 + jae @f + stosb + xor al, al + ret +@@: cmp ax, 256 + jne @f + ; end of block + mov al, 1 + ret +@@: ; substring from sliding dictionary + sub eax, 257 + ; possibly get more bits from length code + xor ecx, ecx + mov cl, byte [length_bits+eax] + xor ebx, ebx + mov bx, word [length_base+2*eax] + call tinf_read_bits + mov word [d_curlen], ax + ; possibly get more bits from distance code + mov ebp, d_dtree + call tinf_decode_symbol + xor ecx, ecx + mov cl, byte [dist_bits+eax] + xor ebx, ebx + mov bx, word [dists_base+2*eax] + call tinf_read_bits + cmp dword [d_dict_ring], 0 + neg eax + mov dword [d_lzOff], eax +.notnull: mov eax, edi + add eax, dword [d_lzOff] + mov al, byte [eax] + stosb +@@: dec word [d_curlen] + xor al, al + ret + +;OUT: +; al: status +tinf_inflate_uncompressed_block: + cmp word [d_curlen], 0 + jne @f + ; get length + lodsw + ; get one's complement of length + add esi, 2 + ; increment length to properly return TINF_DONE below, without + ; producing data at the same time + mov word [d_curlen], ax + inc word [d_curlen] + ; make sure we start next block on a byte boundary + mov byte [d_bitcount], 0 +@@: dec byte [d_curlen] + cmp byte [d_curlen], 0 + jnz @f + mov al, 1 + ret +@@: movsb + xor al, al + ret + +;OUT: +; al,zeroflag bit +tinf_getbit: + mov al, byte [d_bitcount] + or al, al + jnz @f + lodsb + mov byte [d_tag], al + mov byte [d_bitcount], 8 +@@: dec byte [d_bitcount] + xor ax, ax + shr byte [d_tag], 1 + adc ax, 0 + ret + +;IN: +; ebx: base +; cl: num +;OUT: +; eax: bits +tinf_read_bits: + push edx + or cl, cl + jz .end + xor eax, eax + xor edx, edx + inc dl +.next: call tinf_getbit + jz @f + add ebx, edx +@@: shl edx, 1 + dec cl + jnz .next +.end: pop edx + mov eax, ebx + ret + +;IN: +; ebp: TINF_TREE +;OUT: +; eax: trans +tinf_decode_symbol: + push edx + xor eax, eax + xor ebx, ebx ;cur + xor ecx, ecx ;len + xor edx, edx ;sum + ; get more bits while code value is above sum +@@: shl ebx, 1 + call tinf_getbit + add ebx, eax + inc ecx + mov al, byte [ebp+ecx] + add edx, eax + sub ebx, eax + jns @b + add edx, ebx + mov ax, word [ebp+2*edx+16] +mov ebp, edx + pop edx + ret + +tinf_err: + mov esi, nogzip + jmp prot_diefunc + +length_bits: + db 0, 0, 0, 0, 0, 0, 0, 0 + db 1, 1, 1, 1, 2, 2, 2, 2 + db 3, 3, 3, 3, 4, 4, 4, 4 + db 5, 5, 5, 5, 0, 0, 0, 0 +length_base: + dw 3, 4, 5, 6, 7, 8, 9, 10 + dw 11, 13, 15, 17, 19, 23, 27, 31 + dw 35, 43, 51, 59, 67, 83, 99, 115 + dw 131, 163, 195, 227, 258, 0, 0 +dist_bits: + db 0, 0, 0, 0, 1, 1, 2, 2 + db 3, 3, 4, 4, 5, 5, 6, 6 + db 7, 7, 8, 8, 9, 9, 10, 10 + db 11, 11, 12, 12, 13, 13, 0, 0 +dists_base: + dw 1, 2, 3, 4, 5, 7, 9, 13 + dw 17, 25, 33, 49, 65, 97, 129, 193 + dw 257, 385, 513, 769, 1025, 1537, 2049, 3073 + dw 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 +clcidx: + db 16, 17, 18, 0, 8, 7, 9, 6 + db 10, 5, 11, 4, 12, 3, 13, 2 + db 14, 1, 15 + +d_btype: db 255