DragonOS/bootloader/loader.asm

836 lines
20 KiB
NASM
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; |==================|
; | 这是loader程序 |
; |==================|
; Created by longjin, 2022/01/17
; 由于实模式下物理地址为CS<<4+IP而从boot的定义中直到loader的CS为0x1000 因此loader首地址为0x10000
org 0x10000
jmp Label_Start
%include 'fat12.inc' ; 将fat12文件系统的信息包含进来
Base_Of_Kernel_File equ 0x00
Offset_Of_Kernel_File equ 0x100000 ; 设置内核文件的地址空间从1MB处开始。大于实模式的寻址空间
Base_Tmp_Of_Kernel_Addr equ 0x00
Offset_Tmp_Of_Kernel_File equ 0x7e00 ; 内核程序的临时转存空间
Memory_Struct_Buffer_Addr equ 0x7e00 ; 内核被转移到最终的内存空间后,原来的临时空间就作为内存结构数据的存储空间
; ==== 临时的全局描述符表 =====
[SECTION gdt]
LABEL_GDT: dd 0,0
LABEL_DESC_CODE32: dd 0x0000FFFF,0x00CF9A00 ; 代码段和数据段的段基地址都设置在0x00000000处 把段限长设置为0xffffffff可以索引32位地址空间
LABEL_DESC_DATA32: dd 0x0000FFFF,0x00CF9200
GdtLen equ $ - LABEL_GDT
; GDTR寄存器是一个6B的结构低2B保存GDT的长度 高4B保存GDT的基地址
GdtPtr dw GdtLen - 1
dd LABEL_GDT
; 这是两个段选择子是段描述符在GDT表中的索引号
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorData32 equ LABEL_DESC_DATA32 - LABEL_GDT
; === IA-32e模式的临时gdt表
[SECTION gdt64]
LABEL_GDT64: dq 0x0000000000000000
LABEL_DESC_CODE64: dq 0x0020980000000000
LABEL_DESC_DATA64: dq 0x0000920000000000
GdtLen64 equ $ - LABEL_GDT64
GdtPtr64 dw GdtLen64-1,
dd LABEL_GDT64
SelectorCode64 equ LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64 equ LABEL_DESC_DATA64 - LABEL_GDT64
[SECTION .s16] ;定义一个名为.s16的段
[BITS 16] ; 通知nasm将要运行在16位宽的处理器上
Label_Start:
mov ax, cs
mov ds, ax ; 初始化数据段寄存器
mov es, ax ; 初始化附加段寄存器
mov ax, 0x00
mov ss, ax ;初始化堆栈段寄存器
mov sp, 0x7c00
;在屏幕上显示 start Loader
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0100 ;在第2行显示
mov cx, 23 ;设置消息长度
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Start_Loader
int 0x10
;jmp $
; 使用A20快速门来开启A20信号线
push ax
in al, 0x92 ; A20快速门使用I/O端口0x92来处理A20信号线
or al, 0x02 ; 通过将0x92端口的第1位置1开启A20地址线
out 0x92, al
pop ax
cli ; 关闭外部中断
db 0x66
lgdt [GdtPtr] ; LGDT/LIDT - 加载全局/中断描述符表格寄存器
; 置位CR0寄存器的第0位开启保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 为fs寄存器加载新的数据段的值
mov ax, SelectorData32
mov fs, ax
; fs寄存器加载完成后立即从保护模式退出。 这样能使得fs寄存器在实模式下获得大于1MB的寻址能力。
mov eax, cr0
and al, 11111110b ; 将第0位置0
mov cr0, eax
sti ; 开启外部中断
; =========在文件系统中搜索 kernel.bin==========
mov word [SectorNo], SectorNumOfRootDirStart ;保存根目录起始扇区号
Label_Search_In_Root_Dir_Begin:
cmp word [RootDirSizeForLoop], 0 ; 比较根目录扇区数量和0的关系。 cmp实际上是进行了一个减法运算
jz Label_No_KernelBin ; 等于0不存在kernel.bin
dec word [RootDirSizeForLoop]
mov ax, 0x00
mov es, ax
mov bx, 0x8000
mov ax, [SectorNo] ;向函数传入扇区号
mov cl, 1
call Func_ReadOneSector
mov si, Kernel_FileName ;向源变址寄存器传入Loader文件的名字
mov di, 0x8000
cld ;由于LODSB的加载方向与DF标志位有关因此需要用CLD清零DF标志位
mov dx, 0x10 ; 每个扇区的目录项的最大条数是(512/32=16,也就是0x10)
Label_Search_For_LoaderBin:
cmp dx, 0
jz Label_Goto_Next_Sector_In_Root_Dir
dec dx
mov cx, 11 ; cx寄存器存储目录项的文件名长度 11B包括了文件名和扩展名但是不包括 分隔符'.'
Label_Cmp_FileName:
cmp cx, 0
jz Label_FileName_Found
dec cx
lodsb ; 把si对应的字节载入al寄存器中然后由于DF为0si寄存器自增
cmp al, byte [es:di] ; 间接取址[es+di]。 也就是进行比较当前文件的名字对应字节和loader文件名对应字节
jz Label_Go_On ; 对应字节相同
jmp Label_Different ; 字节不同,不是同一个文件
Label_Go_On:
inc di
jmp Label_Cmp_FileName
Label_Different:
and di, 0xffe0 ;将di恢复到当前目录项的第0字节
add di, 0x20 ;将di跳转到下一目录项的第0字节
mov si, Kernel_FileName
jmp Label_Search_For_LoaderBin ;继续搜索下一目录项
Label_Goto_Next_Sector_In_Root_Dir:
add word [SectorNo], 1
jmp Label_Search_In_Root_Dir_Begin
Label_No_KernelBin:
; 在屏幕上显示 [ERROR] No Kernel Found.
mov ax, 0x1301
mov bx, 0x000c ; 红色闪烁高亮黑底
mov dx, 0x0200 ; 显示在第3行前面已经显示过2行了
mov cx, 24 ; 字符串长度
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_No_Loader
int 0x10
jmp $
; ========= 找到了 kernel.bin ===========
; 将内核加载到内存中
Label_FileName_Found:
mov ax, RootDirSectors
; 先取得目录项DIR_FstClus字段的值起始簇号
and di, 0xffe0
add di, 0x1a
mov cx, word [es:di]
push cx
add cx, ax
add cx, SectorBalance
mov eax, Base_Tmp_Of_Kernel_Addr ; 内核放置的临时地址
mov es, eax ;配置es和bx指定kernel.bin在内存中的起始地址
mov bx, Offset_Tmp_Of_Kernel_File
mov ax, cx
Label_Go_On_Loading_File:
push ax
push bx
; 显示字符.
mov ah, 0x0e
mov al, "."
mov bl, 0x0f
int 0x10
pop bx
pop ax
; 读取一个扇区
mov cl, 1
call Func_ReadOneSector
pop ax
; ======逐字节将内核程序复制到临时空间,然后转存到内核空间===
push cx
push eax
push fs
push edi
push ds
push esi
mov cx, 0x0200 ; 指定计数寄存器的值为512 为后面循环搬运这个扇区的数据做准备
mov ax, Base_Of_Kernel_File
mov fs, ax ; 这样在物理机上是行不通的因为这样移动的话fs就失去了32位寻址能力
mov edi, dword [OffsetOfKernelFileCount] ; 指定目的变址寄存器
mov ax, Base_Tmp_Of_Kernel_Addr
mov ds, ax
mov esi, Offset_Tmp_Of_Kernel_File ; 指定来源变址寄存器
Label_Move_Kernel:
; 真正进行数据的移动
mov al, byte [ds:esi] ; 移动到临时区域
mov byte [fs:edi], al ; 再移动到目标区域
inc esi
inc edi
loop Label_Move_Kernel
; 当前扇区数据移动完毕
mov eax, 0x1000
mov ds, eax
mov dword [OffsetOfKernelFileCount], edi ; 增加偏移量
pop esi
pop ds
pop edi
pop fs
pop eax
pop cx
call Func_GetFATEntry
cmp ax, 0x0fff
jz Label_File_Loaded
push ax
mov dx, RootDirSectors
add ax, dx
add ax, SectorBalance
; 继续读取下一个簇
jmp Label_Go_On_Loading_File
Label_File_Loaded:
;在屏幕上显示 kernel loaded
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0200 ;在第3行显示
mov cx, 20 ;设置消息长度
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Kernel_Loaded
int 0x10
; ======直接操作显示内存=======
; 从内存的0x0B800开始是一段用于显示字符的内存空间。
; 每个字符占用2bytes低字节保存要显示的字符高字节保存样式
mov ax, 0xB800
mov gs, ax
mov ah, 0x0F ;黑底白字
mov al, '.'
mov [gs:((80 * 2 + 20) * 2)], ax ;在屏幕第0行39列
Label_Kill_Motor:
; =====关闭软驱的马达======
; 向IO端口0x03f2写入0关闭所有软驱
push dx
mov dx, 0x03F2
mov al, 0
out dx, al
pop dx
; =====获取物理地址空间====
; 显示 正在获取内存结构
mov ax, 0x1301
mov bx, 0x000F
mov dx, 0x0300 ; 在第四行显示
mov cx, 34
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Start_Get_Mem_Struct
int 0x10
mov ax, 0x00
mov es, ax
mov di, Memory_Struct_Buffer_Addr ; 设置内存结构信息存储的地址
mov ebx, 0 ;第一次调用0x15的时候ebx要置为0 ebx存储的是下一个待返回的ARDS Address Range Descriptor Structure
Label_Get_Mem_Struct:
;==== 获取内存物理地址信息
; 使用0x15中断程序的功能号0xe820来获取内存信息
; 返回信息在[es:di]指向的内存中
; 一共要分5次才能把20个字节的信息获取完成
; 这些信息在内核初始化内存管理单元的时候,会去解析它们。
mov eax, 0xe820
mov ecx, 20 ; 指定ARDS结构的大小是固定值20
mov edx, 0x534d4150 ; 固定签名标记是字符串“SMAP”的ASCII码
int 0x15
jc Label_Get_Mem_Fail ; 若调用出错则CF=1
add di, 20
cmp ebx, 0
jne Label_Get_Mem_Struct ; ebx不为0
jmp Label_Get_Mem_OK ; 获取内存信息完成
Label_Get_Mem_Fail:
; =====获取内存信息失败====
; 显示 正在获取内存结构
mov ax, 0x1301
mov bx, 0x000c
mov dx, 0x0400 ; 在第5行显示
mov cx, 33
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Get_Mem_Failed
int 0x10
jmp $
Label_Get_Mem_OK:
; ==== 成功获取内存信息 ===
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0400 ; 在第5行显示
mov cx, 38
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Get_Mem_Success
int 0x10
jmp Label_Get_SVGA_Info
Label_Get_SVGA_Info:
; ==== 获取SVGA芯片的信息
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0500 ; 在第6行显示
mov cx, 34
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Start_Get_SVGA_VBE_Info
int 0x10
; 使用INT0x10的主功能号0x4F00获取SVGA VBE信息
; For more information, please visit: https://longjin666.top/?p=1321
mov ax, 0x00
mov es, ax
mov di, 0x8000
mov ax, 0x4F00
int 0x10
cmp ax, 0x004F ; 获取成功
jz Label_Get_SVGA_VBE_Success
Label_Get_SVGA_VBE_Failed:
; 获取SVGA VBE信息失败
mov ax, 0x1301
mov bx, 0x008c
mov dx, 0x0600 ; 在第7行显示
mov cx, 33
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Get_SVGA_VBE_Failed
int 0x10
jmp $
Label_Get_SVGA_VBE_Success:
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0600 ; 在第7行显示
mov cx, 38
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Get_SVGA_VBE_Success
int 0x10
Label_Get_SVGA_Mode_Info:
; ====== 获取SVGA mode信息 ======
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0700 ; 在第8行显示
mov cx, 35
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Start_Get_SVGA_Mode_Info
int 0x10
mov ax, 0x00
mov es, ax
mov si, 0x800e ; 根据文档可知偏移量0Eh处 DWORD pointer to list of supported VESA and OEM video modes
;(list of words terminated with FFFFh)
mov esi, dword [es:si]
mov edi, 0x8200
Label_SVGA_Mode_Info_Get:
mov cx, word [es:esi]
; ===========显示SVGA mode的信息
;push ax
;mov ax, 0x00
;mov al, ch
;call Label_DispAL
;mov ax, 0x00
;mov al, cl
;call Label_DispAL
;pop ax
;============
; 判断是否获取完毕
cmp cx, 0xFFFF
jz Label_SVGA_Mode_Info_Finish
mov ax, 0x4f01 ; 使用4f01功能获取SVGA的模式
int 0x10
cmp ax, 0x004f ; 判断是否获取成功
jnz Label_SVGA_Mode_Info_Fail
add esi, 2
add edi, 0x100 ; 开辟一个 256-byte 的 buffer
jmp Label_SVGA_Mode_Info_Get
Label_SVGA_Mode_Info_Fail:
; === 获取信息失败 ===
mov ax, 0x1301
mov bx, 0x008c
mov dx, 0x0800 ; 在第9行显示
mov cx, 34
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Get_SVGA_Mode_Failed
int 0x10
jmp $
Label_SVGA_Mode_Info_Finish:
; === 成功获取SVGA mode信息 ===
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x0800 ; 在第9行显示
mov cx, 39
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Get_SVGA_Mode_Success
int 0x10
jmp Label_Set_SVGA_Mode
Label_SET_SVGA_Mode_VESA_VBE_FAIL:
; 设置SVGA显示模式失败
mov ax, 0x1301
mov bx, 0x008c
mov dx, 0x0800 ; 在第10行显示
mov cx, 29
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Message_Set_SVGA_Mode_Failed
int 0x10
jmp $
Label_Set_SVGA_Mode:
; ===== 设置SVGA芯片的显示模式(VESA VBE) ===
mov ax, 0x4f02 ; 使用int0x10 功能号AX=4f02设置SVGA芯片的显示模式
mov bx, 0x4180 ; 显示模式可以选择0x180(1440*900 32bit)或者0x143(800*600 32bit)
int 0x10
cmp ax, 0x004F
jnz Label_SET_SVGA_Mode_VESA_VBE_FAIL
; ===== 初始化GDT表切换到保护模式 =====
cli ; 关闭外部中断
db 0x66
lgdt [GdtPtr]
db 0x66
lidt [IDT_POINTER]
mov eax, cr0
or eax, 1 ; 启用保护模式
mov cr0, eax
; 跳转到保护模式下的第一个程序
jmp dword SelectorCode32:GO_TO_TMP_Protect
[SECTION .s32]
[BITS 32]
GO_TO_TMP_Protect:
; ==== 切换到长模式 =====
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov esp, 0x7e00 ; 将栈指针设置在实模式获取到的数据的基地址上
call support_long_mode ; 检测是否支持长模式
test eax, eax ; 将eax自身相与检测是否为0test指令不会把结果赋值回去eax
jz no_support ; 不支持长模式
; 初始化临时页表, 基地址设置为0x90000
; 设置各级页表项的值(页表起始地址与页属性组成)
mov dword [0x90000], 0x91007
mov dword [0x90004], 0x00000
mov dword [0x90800], 0x91007
mov dword [0x90804], 0x00000
mov dword [0x91000], 0x92007
mov dword [0x91004], 0x00000
mov dword [0x92000], 0x000083
mov dword [0x92004], 0x000000
mov dword [0x92008], 0x200083
mov dword [0x9200c], 0x000000
mov dword [0x92010], 0x400083
mov dword [0x92014], 0x000000
mov dword [0x92018], 0x600083
mov dword [0x9201c], 0x000000
mov dword [0x92020], 0x800083
mov dword [0x92024], 0x000000
mov dword [0x92028], 0xa00083
mov dword [0x9202c], 0x000000
; === 加载GDT ===
db 0x66
lgdt [GdtPtr64] ; 加载GDT
; 把临时gdt的数据段加载到寄存器中(cs除外)
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x7e00
; ====== 开启物理地址扩展 =====
; 通过bts指令将cr4第5位置位开启PAE
mov eax, cr4
bts eax, 5
mov cr4, eax
; 将临时页目录的地址设置到CR3控制寄存器中
mov eax, 0x90000
mov cr3, eax
; ==== 启用长模式 ===
; 参见英特尔开发手册合集p4360 volume4, chapter2 2-60 Vol. 4
; IA32_EFER寄存器的第8位是LME标志位能启用IA-32e模式
mov ecx, 0xC0000080
rdmsr
bts eax, 8
wrmsr
; === 开启分页机制 ===
mov eax, cr0
bts eax, 0 ; 再次开启保护模式
bts eax, 31 ; 开启分页管理机制
mov cr0, eax
; === 通过此条远跳转指令处理器跳转到内核文件进行执行正式进入IA-32e模式
jmp SelectorCode64:Offset_Of_Kernel_File
support_long_mode:
; ===== 检测是否支持长模式 ====
mov eax, 0x80000000
cpuid ; cpuid指令返回的信息取决于eax的值。当前返回到eax中的是最大的输入参数值。 详见英特尔开发人员手册卷2A Chapter3 (Page 304)
cmp eax, 0x80000001
setnb al ; 当cmp结果为不低于时置位al
jb support_long_mode_done ; 当eax小于0x80000001时跳转
mov eax, 0x80000001
cpuid ; 获取特定信息参照开发人员手册卷2A p304
bt edx, 29 ; 将edx第29位的值移到CF上。该位指示了CPU是否支持IA-32e模式
; Bit 29: Intel® 64 Architecture available if 1.
setc al ; 若支持则al置位
support_long_mode_done:
movzx eax, al ; 将al零扩展为32位赋值给eax
ret
no_support:
; 不支持长模式
jmp $
[SECTION .s16lib]
[BITS 16]
; 从软盘读取一个扇区
; AX=待读取的磁盘起始扇区号
; CL=读入的扇区数量
; ES:BX=>目标缓冲区起始地址
Func_ReadOneSector:
push bp
mov bp, sp
sub esp, 2
mov byte [bp-2], cl
push bx
mov bl, [BPB_SecPerTrk]
div bl ;用AX寄存器中的值除以BL得到目标磁道号(商AL)以及目标磁道内的起始扇区号(余数AH)
inc ah ; 由于磁道内的起始扇区号从1开始计数因此将余数+1
mov cl, ah
mov dh, al
shr al, 1 ;计算出柱面号
mov ch, al
and dh, 1;计算出磁头号
pop bx
mov dl, [BS_DrvNum]
;最终dh存储了磁头号dl存储驱动器号
; ch存储柱面号cl存储起始扇区号
Label_Go_On_Reading:
; 使用BIOS中断服务程序INT13h的主功能号AH=02h实现软盘读取操作
mov ah, 2
mov al, byte [bp-2]
int 0x13
jc Label_Go_On_Reading ;当CF标志位被复位时说明数据读取完成恢复调用现场
add esp, 2
pop bp
ret
; 解析FAT表项,根据当前FAT表项索引出下一个FAT表项
Func_GetFATEntry:
; AX=FAT表项号输入、输出参数
; 保存将要被修改的寄存器
push es
push bx
push ax
; 扩展段寄存器
mov ax, 00
mov es, ax
pop ax
mov byte [Odd], 0 ;将奇数标志位置0
; 将FAT表项号转换为总的字节号
mov bx, 3
mul bx
mov bx, 2
div bx
cmp dx, 0
jz Label_Even ; 偶数项
mov byte [Odd], 1
Label_Even:
xor dx, dx ;把dx置0
; 计算得到扇区号(商)和扇区内偏移(余数)
mov bx, [BPB_BytesPerSec]
div bx
push dx
; 读取两个扇区到[es:bx]
mov bx, 0x8000
add ax, SectorNumOfFAT1Start
mov cl, 2 ; 设置读取两个扇区解决FAT表项跨扇区的问题
call Func_ReadOneSector
pop dx
add bx, dx
mov ax, [es:bx]
cmp byte [Odd], 1
jnz Label_Even_2 ;若是偶数项,则跳转
shr ax, 4 ; 解决奇偶项错位问题
Label_Even_2:
and ax, 0x0fff ; 确保表项号在正确的范围内 0x0003~0x0fff
pop bx
pop es
ret
; ==== 显示AL中的信息 ===
Label_DispAL:
push ecx
push edx
push edi
mov edi, [DisplayPosition]
mov ah, 0x0F
mov dl, al ; 为了先显示al的高4位因此先将al暂存在dl中然后把al往右移动4位
shr al, 4
mov ecx, 2 ; 计数为2
.begin:
and al, 0x0F
cmp al, 9
ja .1 ; 大于9跳转到.1
add al, '0'
jmp .2
.1:
sub al, 0x0a
add al, 'A'
.2:
; 移动到显示内存中
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
mov [DisplayPosition], edi
pop edi
pop edx
pop ecx
ret
; === 临时的中断描述符表 ===
; 为临时的IDT开辟空间。
; 由于模式切换过程中已经关闭了外部中断只要确保模式切换过程中不产生异常就不用完整的初始化IDT。甚至乎只要没有异常产生没有IDT也可以。
IDT:
times 0x50 dq 0
IDT_END:
IDT_POINTER:
dw IDT_END - IDT - 1
dd IDT
;==== 临时变量 =====
RootDirSizeForLoop dw RootDirSectors
SectorNo dw 0
Odd db 0
OffsetOfKernelFileCount dd Offset_Of_Kernel_File
DisplayPosition dd 0
; 要显示的消息文本
Message_Start_Loader: db "[DragonOS] Start Loader"
Message_No_Loader: db "[ERROR] No Kernel Found."
Message_Kernel_Loaded: db "[INFO] Kernel loaded"
Message_Start_Get_Mem_Struct: db "[INFO] Try to get memory struct..."
Message_Get_Mem_Failed: db "[ERROR] Get memory struct failed."
Message_Get_Mem_Success: db "[INFO] Successfully got memory struct."
Message_Start_Get_SVGA_VBE_Info: db "[INFO] Try to get SVGA VBE info..."
Message_Get_SVGA_VBE_Failed: db "[ERROR] Get SVGA VBE info failed."
Message_Get_SVGA_VBE_Success: db "[INFO] Successfully got SVGA VBE info."
Message_Start_Get_SVGA_Mode_Info: db "[INFO] Try to get SVGA mode info..."
Message_Get_SVGA_Mode_Failed: db "[ERROR] Get SVGA Mode info failed."
Message_Get_SVGA_Mode_Success: db "[INFO] Successfully got SVGA Mode info."
Message_Set_SVGA_Mode_Failed: db "[ERROR] Set SVGA Mode failed."
Kernel_FileName: db "KERNEL BIN", 0