Linux i386 Boot Code HOWTO
Prev |  | Next

# 3. linux/arch/i386/boot/bootsect.S

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.

## 3.1. Move Bootsect

    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.

## 3.2. Get Disk Parameters

    ///////////////////////////////////////////////////////////////////////////////
    // 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.

## 3.3. Load Setup Code

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.

    ///////////////////////////////////////////////////////////////////////////////
    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 for read_track() and set_next() details.

## 3.4. Load Compressed Image

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 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.

## 3.5. Go Setup

    ///////////////////////////////////////////////////////////////////////////////
    // 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.

## 3.6. Read Disk

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 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. */
    #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:
    }

## 3.7. Bootsect Helper

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.

## 3.8. Miscellaneous

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

## 3.9. Reference

- THE LINUX/I386 BOOT PROTOCOL: `linux/Documentation/i386/boot.txt`

- IA-32 Intel Architecture Software Developer's Manual

- Ralf Brown's Interrupt List

As <IA-32 Intel Architecture Software Developer's Manual> is widely referenced in this document, I will call it "IA-32 Manual" for short.

Prev | Home | Next
Linux Makefiles |  | linux/arch/i386/boot/setup.S