Given that we are booting up bzImage, which is
composed of bbootsect, bsetup
and bvmlinux (head.o, misc.o, piggy.o),
the first floppy sector, bbootsect (512 bytes),
which is compiled from linux/arch/i386/boot/bootsect.S
,
is loaded by BIOS to 07C0:0.
The reset of bzImage (bsetup
and bvmlinux) has not been loaded yet.
SETUPSECTS = 4 /* default nr of setup-sectors */ BOOTSEG = 0x07C0 /* original address of boot-sector */ INITSEG = DEF_INITSEG (0x9000) /* we move boot here - out of the way */ SETUPSEG = DEF_SETUPSEG (0x9020) /* setup starts here */ SYSSEG = DEF_SYSSEG (0x1000) /* system loaded at 0x10000 (65536) */ SYSSIZE = DEF_SYSSIZE (0x7F00) /* system size: # of 16-byte clicks */ /* to be loaded */ ROOT_DEV = 0 /* ROOT_DEV is now written by "build" */ SWAP_DEV = 0 /* SWAP_DEV is now written by "build" */ .code16 .text /////////////////////////////////////////////////////////////////////////////// _start: { // move ourself from 0x7C00 to 0x90000 and jump there. move BOOTSEG:0 to INITSEG:0 (512 bytes); goto INITSEG:go; }
bbootsect has been moved to INITSEG:0 (0x9000:0). Now we can forget BOOTSEG.
/////////////////////////////////////////////////////////////////////////////// // prepare stack and disk parameter table go: { SS:SP = INITSEG:3FF4; // put stack at INITSEG:0x4000-12 /* 0x4000 is an arbitrary value >= * length of bootsect + length of setup + room for stack; * 12 is disk parm size. */ copy disk parameter (pointer in 0:0078) to INITSEG:3FF4 (12 bytes); // int1E: SYSTEM DATA - DISKETTE PARAMETERS patch sector count to 36 (offset 4 in parameter table, 1 byte); set disk parameter table pointer (0:0078, int1E) to INITSEG:3FF4; }
Make sure SP is initialized immediately after SS register. The recommended method of modifying SS is to use "lss" instruction according to IA-32 Intel Architecture Software Developer's Manual (Vol.3. Ch.5.8.3. Masking Exceptions and Interrupts When Switching Stacks).
Stack operations, such as push and pop, will be OK now. First 12 bytes of disk parameter have been copied to INITSEG:3FF4.
/////////////////////////////////////////////////////////////////////////////// // get disk drive parameters, specifically number of sectors/track. char disksizes[] = {36, 18, 15, 9}; int sectors; { SI = disksizes; // i = 0; do { probe_loop: sectors = DS:[SI++]; // sectors = disksizes[i++]; if (SI>=disksizes+4) break; // if (i>=4) break; int13/AH=02h(AL=1, ES:BX=INITSEG:0200, CX=sectors, DX=0); // int13/AH=02h: DISK - READ SECTOR(S) INTO MEMORY } while (failed to read sectors); }
"lodsb" loads a byte from DS:[SI] to AL and increases SI automatically.
The number of sectors per track has been saved in variable sectors.
bsetup (setup_sects sectors) will be loaded right after bbootsect, i.e. SETUPSEG:0. Note that INITSEG:0200==SETUPSEG:0 and setup_sects has been changed by tools/build to match bsetup size in Section 2.6, “linux/arch/i386/tools/build.c”.
/////////////////////////////////////////////////////////////////////////////// got_sectors: word sread; // sectors read for current track char setup_sects; // overwritten by tools/build { print out "Loading"; /* int10/AH=03h(BH=0): VIDEO - GET CURSOR POSITION AND SIZE * int10/AH=13h(AL=1, BH=0, BL=7, CX=9, DH=DL=0, ES:BP=INITSEG:$msg1): * VIDEO - WRITE STRING */ // load setup-sectors directly after the moved bootblock (at 0x90200). SI = &sread; // using SI to index sread, head and track sread = 1; // the boot sector has already been read int13/AH=00h(DL=0); // reset FDC BX = 0x0200; // read bsetup right after bbootsect (512 bytes) do { next_step: /* to prevent cylinder crossing reading, * calculate how many sectors to read this time */ uint16 pushw_ax = AX = MIN(sectors-sread, setup_sects); no_cyl_crossing: read_track(AL, ES:BX); // AX is not modified // set ES:BX, sread, head and track for next read_track() set_next(AX); setup_sects -= pushw_ax; // rest - for next step } while (setup_sects); }
SI is set to the address of sread to index variables sread, head and track, as they are contiguous in memory. Check Section 3.6, “Read Disk” for read_track() and set_next() details.
bvmlinux (head.o, misc.o, piggy.o) will be loaded at 0x100000, syssize*16 bytes.
/////////////////////////////////////////////////////////////////////////////// // load vmlinux/bvmlinux (head.o, misc.o, piggy.o) { read_it(ES=SYSSEG); kill_motor(); // turn off floppy drive motor print_nl(); // print CR LF }
Check Section 3.6, “Read Disk” for read_it() details. If we are booting up zImage, vmlinux is loaded at 0x10000 (SYSSEG:0).
bzImage (bbootsect, bsetup, bvmlinux) is in the memory as a whole now.
/////////////////////////////////////////////////////////////////////////////// // check which root-device to use and jump to setup.S int root_dev; // overwritten by tools/build { if (!root_dev) { switch (sectors) { case 15: root_dev = 0x0208; // /dev/ps0 - 1.2Mb break; case 18: root_dev = 0x021C; // /dev/PS0 - 1.44Mb break; case 36: root_dev = 0x0220; // /dev/fd0H2880 - 2.88Mb break; default: root_dev = 0x0200; // /dev/fd0 - auto detect break; } } // jump to the setup-routine loaded directly after the bootblock goto SETUPSEG:0; }
It passes control to bsetup. See linux/arch/i386/boot/setup.S:start in Section 4, “linux/arch/i386/boot/setup.S”.
The following functions are used to load bsetup and bvmlinux from disk. Note that syssize has been changed by tools/build in Section 2.6, “linux/arch/i386/tools/build.c” too.
sread: .word 0 # sectors read of current track head: .word 0 # current head track: .word 0 # current track /////////////////////////////////////////////////////////////////////////////// // load the system image at address SYSSEG:0 read_it(ES=SYSSEG) int syssize; /* system size in 16-bytes, * overwritten by tools/build */ { if (ES & 0x0fff) die; // not 64KB aligned BX = 0; for (;;) { rp_read: #ifdef __BIG_KERNEL__ bootsect_helper(ES:BX); /* INITSEG:0220==SETUPSEG:0020 is bootsect_kludge, * which contains pointer SETUPSEG:bootsect_helper(). * This function initializes some data structures * when it is called for the first time, * and moves SYSSEG:0 to 0x100000, 64KB each time, * in the following calls. * See Section 3.7, “Bootsect Helper”. */ #else AX = ES - SYSSEG + ( BX >> 4); // how many 16-bytes read #endif if (AX > syssize) return; // everything loaded ok1_read: /* Get proper AL (sectors to read) for this time * to prevent cylinder crossing reading and BX overflow. */ AX = sectors - sread; CX = BX + (AX << 9); // 1 sector = 2^9 bytes if (CX overflow && CX!=0) { // > 64KB AX = (-BX) >> 9; } ok2_read: read_track(AL, ES:BX); set_next(AX); } } /////////////////////////////////////////////////////////////////////////////// // read disk with parameters (sread, track, head) read_track(AL sectors, ES:BX destination) { for (;;) { printf("."); // int10/AH=0Eh: VIDEO - TELETYPE OUTPUT // set CX, DX according to (sread, track, head) DX = track; CX = sread + 1; CH = DL; DX = head; DH = DL; DX &= 0x0100; int13/AH=02h(AL, ES:BX, CX, DX); // int13/AH=02h: DISK - READ SECTOR(S) INTO MEMORY if (read disk success) return; // "addw $8, %sp" is to cancel previous 4 "pushw" operations. bad_rt: print_all(); // print error code, AX, BX, CX and DX int13/AH=00h(DL=0); // reset FDC } } /////////////////////////////////////////////////////////////////////////////// // set ES:BX, sread, head and track for next read_track() set_next(AX sectors_read) { CX = AX; // sectors read AX += sread; if (AX==sectors) { head = 1 ^ head; // flap head between 0 and 1 if (head==0) track++; ok4_set: AX = 0; } ok3_set: sread = AX; BX += CX && 9; if (BX overflow) { // > 64KB ES += 0x1000; BX = 0; } set_next_fn: }
setup.S:bootsect_helper() is only used by bootsect.S:read_it().
Because bbootsect and bsetup
are linked separately, they use offsets relative to
their own code/data segments.
We have to "call far" (lcall) for bootsect_helper()
in different segment, and it must "return far" (lret) then.
This results in CS change in calling, which makes CS!=DS, and
we have to use segment modifier to specify variables in
setup.S
.
/////////////////////////////////////////////////////////////////////////////// // called by bootsect loader when loading bzImage bootsect_helper(ES:BX) bootsect_es = 0; // defined in setup.S type_of_loader = 0; // defined in setup.S { if (!bootsect_es) { // called for the first time type_of_loader = 0x20; // bootsect-loader, version 0 AX = ES >> 4; *(byte*)(&bootsect_src_base+2) = AH; bootsect_es = ES; AX = ES - SYSSEG; return; } bootsect_second: if (!BX) { // 64KB full // move from SYSSEG:0 to destination, 64KB each time int15/AH=87h(CX=0x8000, ES:SI=CS:bootsect_gdt); // int15/AH=87h: SYSTEM - COPY EXTENDED MEMORY if (failed to copy) { bootsect_panic() { prtstr("INT15 refuses to access high mem, " "giving up."); bootsect_panic_loop: goto bootsect_panic_loop; // never return } } ES = bootsect_es; // reset ES to always point to 0x10000 *(byte*)(&bootsect_dst_base+2)++; } bootsect_ex: // have the number of moved frames (16-bytes) in AX AH = *(byte*)(&bootsect_dst_base+2) << 4; AL = 0; } /////////////////////////////////////////////////////////////////////////////// // data used by bootsect_helper() bootsect_gdt: .word 0, 0, 0, 0 .word 0, 0, 0, 0 bootsect_src: .word 0xffff bootsect_src_base: .byte 0x00, 0x00, 0x01 # base = 0x010000 .byte 0x93 # typbyte .word 0 # limit16,base24 =0 bootsect_dst: .word 0xffff bootsect_dst_base: .byte 0x00, 0x00, 0x10 # base = 0x100000 .byte 0x93 # typbyte .word 0 # limit16,base24 =0 .word 0, 0, 0, 0 # BIOS CS .word 0, 0, 0, 0 # BIOS DS bootsect_es: .word 0 bootsect_panic_mess: .string "INT15 refuses to access high mem, giving up."
Note that type_of_loader value is changed. It will be referenced in Section 4.3, “Check Loader Type”.
The rest are supporting functions, variables and part of "real-mode kernel header". Note that data is in .text segment as code, thus it can be properly initialized when loaded.
/////////////////////////////////////////////////////////////////////////////// // some small functions print_all(); /* print error code, AX, BX, CX and DX */ print_nl(); /* print CR LF */ print_hex(); /* print the word pointed to by SS:BP in hexadecimal */ kill_motor() /* turn off floppy drive motor */ { #if 1 int13/AH=00h(DL=0); // reset FDC #else outb(0, 0x3F2); // outb(val, port) #endif } /////////////////////////////////////////////////////////////////////////////// sectors: .word 0 disksizes: .byte 36, 18, 15, 9 msg1: .byte 13, 10 .ascii "Loading"
Bootsect trailer, which is a part of "real-mode kernel header", begins at offset 497.
.org 497 setup_sects: .byte SETUPSECS // overwritten by tools/build root_flags: .word ROOT_RDONLY syssize: .word SYSSIZE // overwritten by tools/build swap_dev: .word SWAP_DEV ram_size: .word RAMDISK vid_mode: .word SVGA_MODE root_dev: .word ROOT_DEV // overwritten by tools/build boot_flag: .word 0xAA55
This "header" must conform to the layout pattern in
linux/Documentation/i386/boot.txt
:
Offset Proto Name Meaning /Size 01F1/1 ALL setup_sects The size of the setup in sectors 01F2/2 ALL root_flags If set, the root is mounted readonly 01F4/2 ALL syssize DO NOT USE - for bootsect.S use only 01F6/2 ALL swap_dev DO NOT USE - obsolete 01F8/2 ALL ram_size DO NOT USE - for bootsect.S use only 01FA/2 ALL vid_mode Video mode control 01FC/2 ALL root_dev Default root device number 01FE/2 ALL boot_flag 0xAA55 magic number
THE LINUX/I386 BOOT PROTOCOL:
linux/Documentation/i386/boot.txt
As <IA-32 Intel Architecture Software Developer's Manual> is widely referenced in this document, I will call it "IA-32 Manual" for short.