2018年3月6日 星期二

linux 0.11 trace boot/setup.s

 .code16
# rewrite with AT&T syntax by falcon  at 081012
#
# setup.s  (C) 1991 Linus Torvalds
#
# setup.s is responsible for getting the system data from the BIOS,
# and putting them into the appropriate places in system memory.
# both setup.s and system has been loaded by the bootblock.
#
# This code asks the bios for memory/disk/other parameters, and
# puts them in a "safe" place: 0x90000-0x901FF, ie where the
# boot-block used to be. It is then up to the protected mode
# system to read them from there before the area is overwritten
# for buffer-blocks.
#

# NOTE! These had better be the same as in bootsect.s!

 .equ INITSEG, 0x9000 # we move boot here - out of the way
 .equ SYSSEG, 0x1000 # system loaded at 0x10000 (65536).
 .equ SETUPSEG, 0x9020 # this is the current segment

 .global _start, begtext, begdata, begbss, endtext, enddata, endbss
 .text
 begtext:
 .data
 begdata:
 .bss
 begbss:
 .text

 ljmp $SETUPSEG, $_start    #boot/bootsetup.s中,會透過ljmp    $SETUPSEG, $0跳到這一行
_start:

# ok, the read went well so we get current cursor position and save it for
# posterity.

 mov $INITSEG, %ax # this is done in bootsect already, but...,這裡原先是放用來執行bootsect的code,
#但現在已經用不到了,改為放從bios讀到的資訊(cursor pos,memory size,...)
 mov %ax, %ds
 mov $0x03, %ah # read cursor pos
 xor %bh, %bh
 int $0x10  # save it in known place, con_init fetches
 mov %dx, %ds:0 # it from 0x90000.
# Get memory size (extended mem, kB)

 mov $0x88, %ah 
 int $0x15
 mov %ax, %ds:2

# Get video-card data:

 mov $0x0f, %ah
 int $0x10
 mov %bx, %ds:4 # bh = display page
 mov %ax, %ds:6 # al = video mode, ah = window width

# check for EGA/VGA and some config parameters

 mov $0x12, %ah
 mov $0x10, %bl
 int $0x10
 mov %ax, %ds:8
 mov %bx, %ds:10
 mov %cx, %ds:12

# Get hd0 data

 mov $0x0000, %ax
 mov %ax, %ds
 lds %ds:4*0x41, %si
 mov $INITSEG, %ax
 mov %ax, %es
 mov $0x0080, %di
 mov $0x10, %cx
 rep
 movsb

# Get hd1 data

 mov $0x0000, %ax
 mov %ax, %ds
 lds %ds:4*0x46, %si
 mov $INITSEG, %ax
 mov %ax, %es
 mov $0x0090, %di
 mov $0x10, %cx
 rep
 movsb

# Check that there IS a hd1 :-)

 mov $0x01500, %ax
 mov $0x81, %dl
 int $0x13
 jc no_disk1
 cmp $3, %ah
 je is_disk1
no_disk1:
 mov $INITSEG, %ax
 mov %ax, %es
 mov $0x0090, %di
 mov $0x10, %cx
 mov $0x00, %ax
 rep
 stosb
is_disk1:

# now we want to move to protected mode ...

 cli   # no interrupts allowed ! 

# first we move the system to it's rightful place,system大小=tools/kernel=121508bytes

 mov $0x0000, %ax
 cld   # 'direction'=0, movs moves forward,SI DI遞增
do_move:    #第一次為將資料從source 0x1000 移到destination 0x0000,移65536bytes,
#之後為source與dest分別加0x1000再做相同的動作,直到src=0x9000時,則跳出迴圈
 mov %ax, %es # destination segment
 add $0x1000, %ax
 cmp $0x9000, %ax    #因為這邊system大小為121508bytes,故最少只須搬兩次(65536*2),即最少只須cmp到$0x3000
 jz end_move
 mov %ax, %ds # source segment
 sub %di, %di
 sub %si, %si
 mov  $0x8000, %cx
 rep
 movsw
 jmp do_move

# then we load the segment descriptors

end_move:
 mov $SETUPSEG, %ax # right, forgot this at first. didn't work :-)
 mov %ax, %ds
 lidt idt_48  # load idt with 0,0
 lgdt gdt_48  # load gdt with whatever appropriate

# that was painless, now we enable A20

 #call empty_8042 # 8042 is the keyboard controller
 #mov $0xD1, %al # command write
 #out %al, $0x64
 #call empty_8042
 #mov $0xDF, %al # A20 on
 #out %al, $0x60
 #call empty_8042
 inb     $0x92, %al # open A20 line(Fast Gate A20).
 orb     $0b00000010, %al
 outb    %al, $0x92

# well, that went ok, I hope. Now we have to reprogram the interrupts :-(
# we put them right after the intel-reserved hardware interrupts, at
# int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
# messed this up with the original PC, and they haven't been able to
# rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
# which is used for the internal hardware interrupts as well. We just
# have to reprogram the 8259's, and it isn't fun.

 mov $0x11, %al  # initialization sequence(ICW1)
     # ICW4 needed(1),CASCADE mode,Level-triggered
 out %al, $0x20  # send it to 8259A-1
 .word 0x00eb,0x00eb  # jmp $+2, jmp $+2
 out %al, $0xA0  # and to 8259A-2
 .word 0x00eb,0x00eb
 mov $0x20, %al  # start of hardware int's (0x20)(ICW2)
 out %al, $0x21  # from 0x20-0x27
 .word 0x00eb,0x00eb
 mov $0x28, %al  # start of hardware int's 2 (0x28)
 out %al, $0xA1  # from 0x28-0x2F
 .word 0x00eb,0x00eb  #               IR 7654 3210
 mov $0x04, %al  # 8259-1 is master(0000 0100) --\
 out %al, $0x21  #    |
 .word 0x00eb,0x00eb  #    INT /
 mov $0x02, %al  # 8259-2 is slave(       010 --> 2)
 out %al, $0xA1
 .word 0x00eb,0x00eb
 mov $0x01, %al  # 8086 mode for both
 out %al, $0x21
 .word 0x00eb,0x00eb
 out %al, $0xA1
 .word 0x00eb,0x00eb
 mov $0xFF, %al  # mask off all interrupts for now
 out %al, $0x21
 .word 0x00eb,0x00eb
 out %al, $0xA1

# well, that certainly wasn't fun :-(. Hopefully it works, and we don't
# need no steenking BIOS anyway (except for the initial loading :-).
# The BIOS-routine wants lots of unnecessary data, and it's less
# "interesting" anyway. This is how REAL programmers do it.
#
# Well, now's the time to actually move into protected mode. To make
# things as simple as possible, we do no register set-up or anything,
# we let the gnu-compiled 32-bit programs do that. We just jump to
# absolute address 0x00000, in 32-bit protected mode.
 #mov $0x0001, %ax # protected mode (PE) bit
 #lmsw %ax  # This is it!
 mov %cr0, %eax # get machine status(cr0|MSW) 
 bts $0, %eax # turn on the PE-bit 
 mov %eax, %cr0 # protection enabled
    
    # segment-descriptor        (INDEX:TI:RPL)
 .equ sel_cs0, 0x0008 # select for code segment 0 (  001:0 :00) 
 ljmp $sel_cs0, $0 # jmp offset 0 of code segment 0 in gdt

# This routine checks that the keyboard command queue is empty
# No timeout is used - if this hangs there is something wrong with
# the machine, and we probably couldn't proceed anyway.
empty_8042:
 .word 0x00eb,0x00eb
 in $0x64, %al # 8042 status port
 test $2, %al  # is input buffer full?
 jnz empty_8042 # yes - loop
 ret

gdt:
 .word 0,0,0,0  # dummy

 .word 0x07FF  # 8Mb - limit=2047 (2048*4096=8Mb)
 .word 0x0000  # base address=0
 .word 0x9A00  # code read/exec
 .word 0x00C0  # granularity=4096, 386

 .word 0x07FF  # 8Mb - limit=2047 (2048*4096=8Mb)
 .word 0x0000  # base address=0
 .word 0x9200  # data read/write
 .word 0x00C0  # granularity=4096, 386

idt_48:
 .word 0   # idt limit=0
 .word 0,0   # idt base=0L

gdt_48:
 .word 0x800   # gdt limit=2048, 256 GDT entries
 .word   512+gdt, 0x9  # gdt base = 0X9xxxx, 
 # 512+gdt is the real gdt after setup is moved to 0x9020 * 0x10
 
.text
endtext:
.data
enddata:
.bss
endbss:

linux 0.11 trace boot/bootsect.s

 .code16
# rewrite with AT&T syntax by falcon  at 081012
#
# SYS_SIZE is the number of clicks (16 bytes) to be loaded.
# 0x3000 is 0x30000 bytes = 196kB, more than enough for current
# versions of linux
#
 .equ SYSSIZE, 0x3000
#
# bootsect.s  (C) 1991 Linus Torvalds
#
# bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
# iself out of the way to address 0x90000, and jumps there.
#
# It then loads 'setup' directly after itself (0x90200), and the system
# at 0x10000, using BIOS interrupts. 
#
# NOTE! currently system is at most 8*65536 bytes long. This should be no
# problem, even in the future. I want to keep it simple. This 512 kB
# kernel size should be enough, especially as this doesn't contain the
# buffer cache as in minix
#
# The loader has been made as simple as possible, and continuos
# read errors will result in a unbreakable loop. Reboot by hand. It
# loads pretty fast by getting whole sectors at a time whenever possible.

 .global _start, begtext, begdata, begbss, endtext, enddata, endbss
 .text
 begtext:
 .data
 begdata:
 .bss
 begbss:
 .text

 .equ SETUPLEN, 4  # nr of setup-sectors
 .equ BOOTSEG, 0x07c0  # original address of boot-sector
 .equ INITSEG, 0x9000  # we move boot here - out of the way
 .equ SETUPSEG, 0x9020  # setup starts here
 .equ SYSSEG, 0x1000  # system loaded at 0x10000 (65536).
 .equ ENDSEG, SYSSEG + SYSSIZE # where to stop loading

# ROOT_DEV: 0x000 - same type of floppy as boot.
#  0x301 - first partition on first drive etc
 .equ ROOT_DEV, 0x301
 ljmp    $BOOTSEG, $_start #跳至cs:eip,這樣一行可以不用加,因為現在的cs即為BOOTSEG(0x07c0)
_start:      #將BOOTSEG上的資料依序往後複製至INITSEG,共複製256回合,每回合複製兩個byte(即一個word),即從BOOTSEG複製512bytes到INITSEG上
 mov $BOOTSEG, %ax
 mov %ax, %ds
 mov $INITSEG, %ax
 mov %ax, %es        #es設定為INITSEG,之後ljmp會用到
 mov $256, %cx
 sub %si, %si
 sub %di, %di
 rep 
 movsw
 ljmp $INITSEG, $go    #跳至INITSEG seg(跳過去後,會執行複製後的bootsect code)
go: mov %cs, %ax       #ax=cs=INITSEG 
 mov %ax, %ds          #ds=ax=INITSEG 
 mov %ax, %es          #es=ax=INITSEG
# put stack at 0x9ff00.
 mov %ax, %ss          #ss=ax=INITSEG=0x9000
 mov $0xFF00, %sp  # arbitrary value >>512   

# load the setup-sectors directly after the bootblock.
# Note that 'es' is already set up.

load_setup:
 mov $0x0000, %dx  # drive 0, head 0
 mov $0x0002, %cx  # sector 2, track 0    #從sector2開始讀(sector2,3放得是setup部分的code)
 mov $0x0200, %bx  # address = 512, in INITSEG
 .equ    AX, 0x0200+SETUPLEN
 mov     $AX, %ax  # service 2, nr of sectors    #ax=0x0204(AH=02h: Read Sectors From Drive AL=Sectors To Read Count)
 int $0x13   # read it
 jnc ok_load_setup  # ok - continue    #會把讀到的兩個sector放在es(INITSEG):bx(0x0200)上,即為SETUPSEG
 mov $0x0000, %dx
 mov $0x0000, %ax  # reset the diskette
 int $0x13
 jmp load_setup

ok_load_setup:

# Get disk drive parameters, specifically nr of sectors/track

 mov $0x00, %dl
 mov $0x0800, %ax  # AH=8 is get drive parameters
 int $0x13
 mov $0x00, %ch
 #seg cs
 mov %cx, %cs:sectors+0 # %cs means sectors is in %cs
 mov $INITSEG, %ax
 mov %ax, %es

# Print some inane message

 mov $0x03, %ah  # read cursor pos
 xor %bh, %bh
 int $0x10
 
 mov $24, %cx
 mov $0x0007, %bx  # page 0, attribute 7 (normal)
 #lea msg1, %bp
 mov     $msg1, %bp
 mov $0x1301, %ax  # write string, move cursor
 int $0x10

# ok, we've written the message, now
# we want to load the system (at 0x10000)

 mov $SYSSEG, %ax
 mov %ax, %es  # segment of 0x010000
 call read_it
 call kill_motor

# After that we check which root-device to use. If the device is
# defined (#= 0), nothing is done and the given device is used.
# Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
# on the number of sectors that the BIOS reports currently.

 #seg cs
 mov %cs:root_dev+0, %ax
 cmp $0, %ax
 jne root_defined
 #seg cs
 mov %cs:sectors+0, %bx
 mov $0x0208, %ax  # /dev/ps0 - 1.2Mb
 cmp $15, %bx
 je root_defined
 mov $0x021c, %ax  # /dev/PS0 - 1.44Mb
 cmp $18, %bx
 je root_defined
undef_root:
 jmp undef_root
root_defined:
 #seg cs
 mov %ax, %cs:root_dev+0

# after that (everyting loaded), we jump to
# the setup-routine loaded directly after
# the bootblock:

 ljmp $SETUPSEG, $0    #跳至SETUPSEG,執行setup code ,需看boot/setup.s

# This routine loads the system at address 0x10000, making sure
# no 64kB boundaries are crossed. We try to load it as fast as
# possible, loading whole tracks whenever we can.
#
# in: es - starting address segment (normally 0x1000)
#
sread: .word 1+ SETUPLEN # sectors read of current track
head: .word 0   # current head
track: .word 0   # current track

read_it:
 mov %es, %ax
 test $0x0fff, %ax
die: jne  die   # es must be at 64kB boundary
 xor  %bx, %bx  # bx is starting address within segment
rp_read:
 mov  %es, %ax
  cmp  $ENDSEG, %ax  # have we loaded all yet?
 jb ok1_read
 ret
ok1_read:
 #seg cs
 mov %cs:sectors+0, %ax
 sub sread, %ax
 mov %ax, %cx
 shl $9, %cx
 add %bx, %cx
 jnc  ok2_read
 je  ok2_read
 xor  %ax, %ax
 sub  %bx, %ax
 shr  $9, %ax
ok2_read:
 call  read_track
 mov  %ax, %cx
 add  sread, %ax
 #seg cs
 cmp  %cs:sectors+0, %ax
 jne  ok3_read
 mov  $1, %ax
 sub  head, %ax
 jne  ok4_read
 incw    track 
ok4_read:
 mov %ax, head
 xor %ax, %ax
ok3_read:
 mov %ax, sread
 shl $9, %cx
 add %cx, %bx
 jnc rp_read
 mov %es, %ax
 add $0x1000, %ax
 mov %ax, %es
 xor %bx, %bx
 jmp rp_read

read_track:
 push %ax
 push %bx
 push %cx
 push %dx
 mov track, %dx
 mov sread, %cx
 inc %cx
 mov %dl, %ch
 mov head, %dx
 mov %dl, %dh
 mov $0, %dl
 and $0x0100, %dx
 mov $2, %ah
 int $0x13
 jc bad_rt
 pop %dx
 pop %cx
 pop %bx
 pop %ax
 ret
bad_rt: mov $0, %ax
 mov $0, %dx
 int $0x13
 pop %dx
 pop %cx
 pop %bx
 pop %ax
 jmp read_track

#/*
# * This procedure turns off the floppy drive motor, so
# * that we enter the kernel in a known state, and
# * don't have to worry about it later.
# */
kill_motor:
 push %dx
 mov $0x3f2, %dx
 mov $0, %al
 outsb
 pop %dx
 ret

sectors:
 .word 0

msg1:
 .byte 13,10
 .ascii "Loading system ..."
 .byte 13,10,13,10

 .org 508
root_dev:
 .word ROOT_DEV
boot_flag:
 .word 0xAA55
 
 .text
 endtext:
 .data
 enddata:
 .bss
 endbss: