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 <file> <offset>
;
; This will write the two bytes 0xaa 0xbb to the file <file> at offset
; <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: