eval :; if [ $# -gt 1 ]; then xxd -p -r | dd conv=notrunc bs=1 seek=0$2 of=$1; fi; exit ; asmsyntax=nasm for vim ; bl.asm ; ; This is a bootloader that fits into the MBR. It iterates through every ; partition until it finds one that is marked bootable. If the partition's ; type is 0x8a (from AiR-Boot) it loads a Linux bzImage. Otherwise it ; chainloads the first sector. ; ; The bootloader supports partitions starting at above or being larger than ; 2 TiB using an extension to the partition table where if bit 6 (0x40) of ; the 'status' field is set, the fields 'chs' and 'chs_end' become the higher ; bytes of the lba start address and size, respectively. I know of no ; partitioning tool which supports this, however. ; ; The code being limited to 446 bytes, no configuration is supported. Since ; you can't pass any parameters to the kernel, you have to configure it by ; manually editing the fields of its header in the bzImage file. The short ; shell script line above helps with this. ; ; $ echo aa bb | sh bl.asm ; ; This will write the two bytes 0xaa 0xbb to the file at offset ; (in decimal). Trying to boot without setting a root device will ; give you some lines like: ; ; 0300 36065 hda driver: ide-gd ; 0301 16033 hda1 ; ; To boot from device 0301, do: ; ; $ echo 01 03 | sh bl.asm bzImage 508 # little-endian ; ; That should be all you need, but all of the header fields are available in ; Documentation/x86/boot.txt of the kernel sources. ; ; To install: ; # nasm bl.asm ; # dd conv=notrunc if=bl of=/dev/drive ; ; We also have to enable the boot signature as bl.asm doesn't contain it or it ; would overwrite the partition table. ; # echo 55aa | sh bl.asm /dev/drive 510 ; ; Installing the kernel: ; # fdisk /dev/device # Mark the partition bootable and set its type to 0x8a. ; # dd conv=notrunc if=bzImage of=/dev/device_partition ; ; You also have to make sure that the kernel partition has the bootable flag on ; and has partition type 0x8a. The warning about an ancient bootloader is ; normal. Don't use this on kernels older than 2.6.14. ; ; An 'e' is printed if your BIOS lacks LBA support or there is no bootable ; partition. A hang can mean that your computer lacks fast A20 or that the ; kernel overwrote reserved memory. Try making your kernel smaller. I get ; roughly 7 MiB on my hardware but unlimited in QEMU (a recent bzImage can be ; anything between 2 and 30 MiB. [BITS 16] [ORG 0x7e00] PE equ 1 BOOTABLE equ 0x80 BIG equ 0x40 ; Describes a disc address packet for int 13h extended read sectors from drive. struc dap .size resb 1 .res0 resb 1 .len resb 1 .res1 resb 1 .offset resw 1 .segment resw 1 .lba resq 1 endstruc ; Describes a record in the MBR partition table struc prec .status resb 1 .chs resb 3 .type resb 1 .chs_end resb 3 .lba resd 1 .lba_end resd 1 endstruc ; The first part of the bzImage header. See Linux Documentation/x86/boot.txt. struc bzImage .code resb 0x1f1 .setup_sects resb 1 .root_flags resw 1 .syssize resd 1 .ram_size resw 1 .vid_mode resw 1 .root_dev resw 1 .boot_flag resw 1 .jump resb 2 .header resd 1 .version resw 1 endstruc ; dl remains the boot device (usually 0x80) throughout the code. intro: ; Set up segments and stack. xor ax, ax mov ds, ax mov es, ax mov ss, ax mov sp, 0x9c00 ; Move code out of the way for chainloading (to just after this, 0x7e00) mov cx, 0x100 lea si, [ds:0x7c00] lea di, [es:0x7e00] rep movsw ; Move to proper code jmp 0x0:start start: ; Check for BIOS LBA support mov ah, 0x41 mov bx, 0x55aa int 0x13 jc no_pc ; Fast A20 enable. in al, 0x92 or al, 0x02 out 0x92, al ; Enable unreal mode, allowing us to access all 4 GiB of memory. cli push ds push es lgdt [gdt] ; Enable protected mode mov eax, cr0 or al, PE mov cr0, eax ; Load ds segment register mov bx, 0x08 mov ds, bx mov es, bx ; Leave protected mode and al, ~PE mov cr0, eax pop es pop ds sti load_mbr: ; Try to boot one of the four primary partitions. xor ecx, ecx xor edi, edi mov cx, 4 mov bx, partitions .try: mov al, [bx+prec.type] test al, al jz .next cmp al, 0x05 je .extended ; Primary partition. Try loading. call load_partition jmp .next .ext_err: call pop_lba jmp .ext_out .extended: ; This is an extended partition. Iterate through all logical partitions. pusha call push_lba call add_lba ; LBA is the first EBR at this point. call push_lba jmp .ext_first .ext_next: ; LBA is the first EBR and the bx record is the link partition entry. call push_lba call add_lba .ext_first: ; Load EBR. mov cl, 1 mov di, 0x1200 call push_lba call read_sects jc .ext_err call pop_lba mov bx, 0x1200+0x1be call load_partition call pop_lba add bx, prec_size cmp byte[bx+prec.type], 0 jne .ext_next .ext_out: call pop_lba popa .next: add bx, prec_size loop .try ; There is no bootable OS (no partition marked bootable). ; The computer hardware is unsupported (BIOS A20 or LBA missing). no_pc: ; Print an 'e'. mov ax, 0x0e65 int 0x10 ; Wait for a key press. xor ax, ax int 0x16 ; Reboot. int 0x19 jmp $ ; Destroys bp push_lba: pop bp push dword[LBA] push dword[LBA+4] push bp ret ; Destroys bp pop_lba: pop bp pop dword[LBA+4] pop dword[LBA] push bp ret ; We have, in bx, a primary or logical partition record. load_partition: ; Add the start sector from the partition record to [LBA]. We add instead of ; just replace because logical partitions have an offset relative to the ; start of the extended partition. call add_lba xor ecx, ecx mov cl, 1 xor edi, edi ; Check bootable flag test byte[bx+prec.status], BOOTABLE jz .out ; Check for linux bzImage partition (type 0x8a, first defined by AiR-Boot). cmp byte[bx+prec.type], 0x8a jne .chainload ; Load kernel boot sector and bzImage header. mov edi, 0x90000 call read_sects jc .out ; Load rest of kernel real-mode setup code to 0x90200. a32 mov cl, [0x90000+bzImage.setup_sects] call read_sects jc .out ; Load protected-mode kernel to 0x100000. a32 mov ecx, [0x90000+bzImage.syssize] shr ecx, 5 inc ecx mov edi, 0x100000 call read_sects jc .out ; Invoke kernel. mov ax, 0x9000 mov ds, ax mov es, ax mov ss, ax jmp 0x9020:0x00 .chainload: ; Generic partition; chainload. mov di, 0x7c00 call read_sects jc .out ; We could check for 55aa here but we don't care. jmp 0x7c0:0x00 .out: ret .ebr_adr db 0x1200/0x200 ; Add partition (at bx) start sector to [LBA]. Destroys eax. add_lba: mov eax, [bx+prec.lba] add [LBA], eax ; Save carry for adc below. lahf xor eax, eax test byte[bx+prec.status], BIG jz .simple ; Extended partition offset. Use the three bytes traditionally reserved for ; CHS as the upper bytes of the LBA if bit 6 (0x40) in prec.status is set. mov eax, [bx+prec.chs] and eax, 0x00ffffff .simple: sahf adc [LBA+4], eax ret read_sects: ; Read ecx sectors of drive dl starting at [LBA] to the buffer at edi. edi ; and [LBA] are updated. ecx and esi are destroyed. .sector: call read_sector jc .out push ecx ; Move sector data from low memory (0x1000) to edi. mov esi, 0x1000 mov ecx, 0x200/4 ; edi is also updated by this. a32 rep movsd ; Increment qword[LBA], one word at a time. mov si, LBA-2 .next: add si, 2 mov ax, [si] inc ax mov [si], ax jz .next pop ecx a32 loop .sector .out: ret read_sector: ; Read a sector from hard drive dl into 0x1000. Specify sector by ; directly changing [LBA]. pusha mov cx, 5 .retry: mov ah, 0x42 mov si, read_dap int 0x13 jnc .out loop .retry .out: popa ret read_dap istruc dap at dap.size, db 0x10 at dap.len, db 1 at dap.offset, dw 0x1000 at dap.segment, dw 0x0 iend LBA equ read_dap+dap.lba gdt: ; This is the unused null descriptor. It doubles as the lgdt argument. dw .end - gdt - 1 dd gdt dw 0 .flat: ; Flat descriptor dw 0x07ff, 0x0000, 0x9200, 0x00c0 .end: ; Make sure the boot code won't be too long. times 446-($-$$) db 0 partitions: