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