[Linux0.11 code analysis] 03 setup.s startup process

[Linux0.11 code analysis] 03 setup.s startup process

  • 1. boot\setup.s

The series of articles are as follows:

Summary of series of articles: “[Linux0.11 Code Analysis] Summary of Links of Series Articles (Full)”
.
1. “[Linux0.11 Code Analysis] 01 Code Directory Analysis”
2. “[Linux0.11 Code Analysis] 02 bootsect.s startup process”
3. “[Linux0.11 Code Analysis] 03 setup.s startup process”
4. “[Linux0.11 code analysis] head.s startup process of 04”
5. “[Linux0.11 Code Analysis] Kernel Initialization init\main.c Code Analysis of 05”
6. “[Linux0.11 code analysis] 06 kernel initialization init process code analysis”

In the previous article, we know that after bootsect.s loads setup.s and system into memory,
Jump to 0x90200 through the jmpi 0,SETUPSEG command to start executing the setup.s program, and hand over the CPU to the code>setup.s in hand.

The main functions of setup.s are as follows:

  1. Get memory, disk and other system data from ROM BIOS, and save these data to 0x90000-0x901FF, it will overwrite bootsect.s where the program is located.
  2. Move the system module from 0x10000 - 0x8FFFF down to the absolute memory address 0x00000
  3. Load interrupt descriptor table register (idtr) and global descriptor table register (gdtr)
  4. Turn on the A20 address line, reset the two interrupt control chips 8259A, and reset the hardware interrupt number to 0x20 - 0x2F
  5. Set the control register CRO (machine status word) of CPU to enter the 32 protected mode to run
  6. Jump to the head.s program running in the front part of the system module

The obtained parameters are as follows:

memory address length (bytes) name description
0x90000 2 Cursor position Column number (0x00-leftmost), line number (0x00-topmost)
0x90002 2 Extended memory number The extended memory value of the system starting from 1M (KB)
0x90004 2 Display page Current display page
0x90006 1 display mode
0x90007 1 Number of character columns
0x90008 2
0x9000A 1 Display Memory Display memory (0x00-64k,0x01-128k,0x02-192k,0x03=256k)
0x9000B 1 display status 0x00-color, I/O=0x3dX ;0x11-monochrome, I/O=0x3bX
0x9000C 2 characteristic parameters display card characteristic parameters
0x90080 16 hard disk parameter table the first hard disk parameter table
0x90090 16 hard disk parameter list The parameter list of the second hard disk (if there is no one, it will be cleared)
0x901FC 2 Root device number The device number where the root file system is located (set in bootsec.s)

1. boot\setup.s

The detailed work is as follows:

  1. Configure data segment register ds = ax = 0x9000
  2. Read the current cursor position, BIOS interrupt 0x10, function number ah=0x03, then save the current cursor position in 0x90000 code> at the first byte
  3. Get the size value of extended memory, BIOS interrupt 0x15, the function number is ah=0x88
  4. Get and display the current display mode, call BIOS interrupt 0x10, function number ah=0x0f
  5. Check the display mode (EGA/VGA) and get the parameters, call the BIOS interrupt 0x10, additional function selection – get the mode information, function number:ah=0x12, bl=0x10
  6. Obtain the hard disk parameter table of the first hard disk, the first address of the 1 hard disk parameter table is the vector value of the interrupt vector 0x41, save at 0x90080 office
  7. Obtain the hard disk parameter table of the second hard disk. The first address of the 2 hard disk parameter table is the vector value of the interrupt vector 0x46, which is stored in 0x90090 office
  8. Check if there is the 2 hard disk in the system, if not, clear the second table at address 0x90090
  9. disable interrupt
  10. Move system from 0x10000-0x8FFFF to 0x00000-0x7FFFF (512k)
  11. Load interrupt descriptor table (idt) register and global descriptor table (gdt) register
  12. Enable A20 address line
  13. Reprogram the interrupts and save them behind the intel reserved hardware interrupts at 0x20-0x2F
  14. Configure to run in 32-bit protected mode, jump to address 0x00000, and start running head.s of the system module

# linux-0.11\boot\setup.s

INITSEG = 0x9000 // The original bootsect.s segment 0x90000 (576kb) ! we move boot here - out of the way
SYSSEG = 0x1000 // The segment where the current system module is located is 0x10000 (64kb) ! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020 // The segment where the current setup.s program is located 0x90200 (576.5kb) ! this is the current segment

.globl begtext, begdata, begbss, endtext, enddata, endbss // define global identifiers
.text // text segment
begtext:
.data // data segment
begdata:
.bss // stack segment
begbss:
.text // text segment

entry start // program entry function start
start:
// 1. Configure the data segment register ds = ax = 0x9000
mov ax, #INITSEG ! this is done in bootsect already, but... ?
mov ds,ax
// 2. Read the current cursor position, BIOS interrupt 0x10, function number ah=0x03, then save the current cursor position at the first byte of 0x90000
// Return: ch=scan start line, cl=scan end line, dh=row number (0x00 is top), dl=column number (0x00 is left).
mov ah,#0x03 ! read cursor pos
xor bh, bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx // 0x90000: column number (0x00 is left), 0x90001: line number (0x00 is top)

// 3. Get the size value of the extended memory, the BIOS interrupt is 0x15, and the function number is ah=0x88
// return ax = the extended memory size (KB) starting from 0x100000 (1M), if there is an error, CF will be set, ax = error code
mov ah,#0x88
int 0x15
mov [2],ax // 0x90002 (1 word) stores the extended memory size (KB) starting from 0x100000 (1M)

// 4. Get and display the current display mode, call BIOS interrupt 0x10, function number ah=0x0f
// Return: ah=number of character columns, al=display mode, bh=current display page
mov ah,#0x0f
int 0x10
mov [4], bx // 0x90004 (1 word) stores the current page ! bh = display page
mov [6],ax // 0x90006 display mode, 0x90007 number of character columns ! al = video mode, ah = window width

// 5. Check the display mode (EGA/VGA) and get parameters, call BIOS interrupt 0x10, additional function selection - get mode information, function number: ah=0x12, bl=0x10
// Returns: bh = display state (0x00 - color mode, I/O port=0x3dX) (0x01 - monochrome mode, I/O port=0x3bX)
// bl = installed display memory (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k)
// cx = display card characteristic parameters
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax // 0x90008
mov [10],bx // 0x9000A display memory installed, 0x9000B display status
mov [12],cx // 0x9000C display card characteristic parameters

// 6. Obtain the hard disk parameter table of the first hard disk. The first address of the first hard disk parameter table is the vector value of the interrupt vector 0x41, which is stored at 0x90080
mov ax,#0x0000
mov ds,ax // configuration data segment register ds = ax = 0x0000
lds si,[4*0x41] // Take the value of interrupt vector 0x41, which is the address of hd0 parameter table ds:si
mov ax,#INITSEG // Configure segment base address as ex = ax = 0x9000
mov es,ax
mov di,#0x0080 // Destination address of transmission: 0x9000:0x0080 es:di
mov cx,#0x10 // total transfer 0x10 bytes
rep
movsb

// 7. Obtain the hard disk parameter table of the second hard disk. The first address of the second hard disk parameter table is the vector value of the interrupt vector 0x46, which is stored at 0x90090
mov ax,#0x0000
mov ds,ax // configuration data segment register ds = ax = 0x0000
lds si,[4*0x46] // Take the value of interrupt vector 0x416, which is the address of hd1 parameter table ds:si
mov ax,#INITSEG // Configure segment base address as ex = ax = 0x9000
mov es,ax
mov di,#0x0090 // Destination address of transmission: 0x9000:0x0090 es:di
mov cx,#0x10 // total transfer 0x10 bytes
rep
movsb

// 8. Check whether there is a second hard disk in the system, if not, clear the second table at address 0x90090
// Use the BIOS interrupt to call the disk fetching function of 0x13, the function number ah = 0x15
// Input: dl = drive letter (0x8X is the hard disk: 0x80 refers to the 1st hard disk, 0x81 refers to the 2nd hard disk)
// Output: ah=type code; 00 does not have this disk, CF is set; 01 is a floppy drive without change-line support; 02 is a floppy drive (or other removable devices) with change-line support; 03 is a hard disk
mov ax,#0x01500
mov dl,#0x81
int 0x13
jc no_disk1
cmp ah,#3 // judge ah==3, judge whether disk1 exists
je is_disk1
no_disk1:
mov ax,#INITSEG //The second hard disk does not exist, then clear the table of the second hard disk.
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
is_disk1:

!now we want to move to protected mode... ?
// 9. Disable interrupt
cli ! no interrupts allowed !

// 10. Move system from 0x10000-0x8ffff to 0x00000-0x7ffff(512k)
mov ax,#0x0000
cld // Clear the direction flag bit DF of the flag register Flag ! 'direction'=0, movs moves forward
do_move:
mov es,ax // destination address es:di = 0x0000:0x0
add ax,#0x1000 // ax = ax + 0x1000
cmp ax,#0x9000 // Determine whether it is equal to 0x9000, if it is equal, it means that the movement is completed
jz end_move
mov ds,ax // source address ds:si = 0x1000:0x0
sub di, di
sub si,si
mov cx,#0x8000 // Move 0x8000 words (64byte)
rep
movsw
jmp do_move

// 11. Load interrupt descriptor table (idt) register and global descriptor table (gdt) register
end_move:
// set data segment register ds = ax = 0x9020
mov ax, #SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax
// Load the interrupt descriptor table (idt) register,
// Its operand is 6 bytes, 0-1 byte is the length value (byte) of the descriptor table; 2-5 bytes are the 32-bit linear base address (first address) of the descriptor table
// Each entry (8 bytes) in the interrupt descriptor table refers to the information of the code that needs to be called when an interrupt occurs, which is somewhat similar to the interrupt vector, but contains more information
lidt idt_48 ! load idt with 0,0
---------------->
+ idt_48:
+ .word 0 ! idt limit=0
+ .word 0,0 ! idt base=0L
<----------------
\t
// load the global descriptor table (gdt) register
// Each descriptor item (8 bytes) in the global descriptor table describes the information of data and code segments (blocks) in protected mode
// Including the maximum length limit of the segment (16 bits), the linear base address of the segment (32 bits), the privilege level of the segment, whether the segment is in memory, read and write permissions, and other signs of protected mode operation.
lgdt gdt_48 ! load gdt with whatever appropriate
---------------->
+ 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
+
+ gdt_48:
+ .word 0x800 ! gdt limit=2048, 256 GDT entries
+ .word 512 + gdt,0x9 ! gdt base = 0X9xxxx
<----------------

! that was painless, now we enable A20
// 12. Enable A20 address line
call empty_8042 // wait for input buffer to be empty
--------------->
+ ! 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 al,#0x64 ! 8042 status port
+ test al,#2 ! is input buffer full?
+ jnz empty_8042 ! yes - loop
+ ret
<---------------
// 0xD1 command code - means to write data to the P2 port of 8042, the bit 1 of the P2 port is used for the strobe of the A20 line, and the data should be written to the 0x60 port
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042 // wait for input buffer to be empty
mov al,#0xDF // strobe the parameter of A20 address line ! A20 on
out #0x60, al
call empty_8042 // wait for input buffer to be empty

// 13. Reprogram the interrupts and save them behind the hardware interrupts reserved by intel, located at 0x20-0x2F
// 0x11 indicates the start of the initialization command, which is the ICW1 command word, which means edge triggering, multi-chip 8259 cascade connection, and finally sends the ICW4 command word.
mov al,#0x11 ! initialization sequence
// send to 8259A main chip
out #0x20, al ! send it to 8259A-1
\t
.word 0x00eb,0x00eb // Delay ! jmp $ + 2, jmp $ + 2
// Then send to 8259A slave chip
out #0xA0, al ! and to 8259A-2
.word 0x00eb,0x00eb // Delay
\t
// Send the main chip ICW2 command word, start interrupt number, odd address to be sent
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21, al
.word 0x00eb,0x00eb // Delay
\t
// Send command word from chip ICW2, start interrupt number
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb // Delay
\t
// Send the command word of the main chip ICW3, the IR2 of the main chip is connected to the slave chip INT
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb // Delay
\t
// Send the ICW3 command word from the slave chip, indicating that the INT of the slave chip is connected to the IR2 pin of the master chip.
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb // Delay
\t
// Send ICW4 command word of two chips. 8086 mode; normal EOI mode, need to send command to reset. The initialization is completed and the chip is ready
mov al,#0x01 ! 8086 mode for both
\t
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb
\t
// currently mask all interrupt requests
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al

// 14. Configure to run in 32-bit protected mode, jump to address 0x00000, and start running head.s of the system module
mov ax,#0x0001 ! protected mode (PE) bit
// Load the machine status word (lmsw - Load Machine Status Word), also known as the control register CR0, setting its bit0 to 1 will cause the CPU to work in protected mode
lmsw ax ! This is it!
// Configure the privilege level as the system level, use the global descriptor, the index item is 0, the offset is 0, that is, jump to the address of 0x00000 to start running
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
\t
// The length of the segment selector is 16 bits (2 bytes);
// Bits 0-1 represent the requested privilege level 0-3, the linux operating system only uses two levels: level 0 (system level) and level 3 (user level);
// bit 2 is used to select global descriptor table (0) or local descriptor table (1)
// Bits 3-15 are the index of the descriptor table entry, indicating which descriptor to select
// So the segment selector 8 (0b0000,0000,0000,1000) means requesting privilege level 0, using item 1 in the global descriptor table