xref: /DragonOS/kernel/src/arch/x86_64/asm/head.S (revision 83ed0ebc293d5a10245089f627f52770fd5b9dd4)
1// 这是内核执行头程序
2// Created by longjin.
3// 2022/01/20
4
5#include "common/asm.h"
6
7// 以下是来自 multiboot2 规范的定义
8//  How many bytes from the start of the file we search for the header.
9#define MULTIBOOT_SEARCH 32768
10#define MULTIBOOT_HEADER_ALIGN 8
11
12//  The magic field should contain this.
13#define MULTIBOOT2_HEADER_MAGIC 0xe85250d6
14
15//  This should be in %eax.
16#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289
17
18//  Alignment of multiboot modules.
19#define MULTIBOOT_MOD_ALIGN 0x00001000
20
21//  Alignment of the multiboot info structure.
22#define MULTIBOOT_INFO_ALIGN 0x00000008
23
24//  Flags set in the 'flags' member of the multiboot header.
25
26#define MULTIBOOT_TAG_ALIGN 8
27#define MULTIBOOT_TAG_TYPE_END 0
28#define MULTIBOOT_TAG_TYPE_CMDLINE 1
29#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2
30#define MULTIBOOT_TAG_TYPE_MODULE 3
31#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4
32#define MULTIBOOT_TAG_TYPE_BOOTDEV 5
33#define MULTIBOOT_TAG_TYPE_MMAP 6
34#define MULTIBOOT_TAG_TYPE_VBE 7
35#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8
36#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9
37#define MULTIBOOT_TAG_TYPE_APM 10
38#define MULTIBOOT_TAG_TYPE_EFI32 11
39#define MULTIBOOT_TAG_TYPE_EFI64 12
40#define MULTIBOOT_TAG_TYPE_SMBIOS 13
41#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14
42#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15
43#define MULTIBOOT_TAG_TYPE_NETWORK 16
44#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17
45#define MULTIBOOT_TAG_TYPE_EFI_BS 18
46#define MULTIBOOT_TAG_TYPE_EFI32_IH 19
47#define MULTIBOOT_TAG_TYPE_EFI64_IH 20
48#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21
49
50#define MULTIBOOT_HEADER_TAG_END 0
51#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1
52#define MULTIBOOT_HEADER_TAG_ADDRESS 2
53#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3
54#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4
55#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5
56#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6
57#define MULTIBOOT_HEADER_TAG_EFI_BS 7
58#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8
59#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9
60#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10
61
62#define MULTIBOOT_ARCHITECTURE_I386 0
63#define MULTIBOOT_ARCHITECTURE_MIPS32 4
64#define MULTIBOOT_HEADER_TAG_OPTIONAL 1
65
66#define MULTIBOOT_LOAD_PREFERENCE_NONE 0
67#define MULTIBOOT_LOAD_PREFERENCE_LOW 1
68#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2
69
70#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1
71#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2
72
73
74
75// 直接用 -m64 编译出来的是 64 位代码,
76// 但是启动后的机器是 32 位的,相当于在 32 位机器上跑 64 位程序。
77// 得加一层跳转到 64 位的 -m32 代码,开启 long 模式后再跳转到以 -m64 编译的代码中
78// 对于 x86_64,需要在启动阶段进入长模式(IA32E),这意味着需要一个临时页表
79// See https://wiki.osdev.org/Creating_a_64-bit_kernel:
80// With a 32-bit bootstrap in your kernel
81
82// 这部分是从保护模式启动 long 模式的代码
83// 工作在 32bit
84// 声明这一段代码以 32 位模式编译
85.code32
86
87// multiboot2 文件头
88// 计算头长度
89.SET HEADER_LENGTH, multiboot_header_end - multiboot_header
90// 计算校验和
91.SET CHECKSUM, -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + HEADER_LENGTH)
92// 8 字节对齐
93.section .multiboot_header
94.align MULTIBOOT_HEADER_ALIGN
95// 声明所属段
96
97multiboot_header:
98    // 魔数
99    .long MULTIBOOT2_HEADER_MAGIC
100    // 架构
101    .long MULTIBOOT_ARCHITECTURE_I386
102    // 头长度
103    .long HEADER_LENGTH
104    // 校验和
105    .long CHECKSUM
106    // 添加其它内容在此,详细信息见 Multiboot2 Specification version 2.0.pdf
107
108// 设置帧缓冲区(同时在这里设置qemu的分辨率, 默认为: 1440*900, 还支持: 640*480, 等)
109.align 8
110framebuffer_tag_start:
111    .short MULTIBOOT_HEADER_TAG_FRAMEBUFFER
112    .short MULTIBOOT_HEADER_TAG_OPTIONAL
113    .long framebuffer_tag_end - framebuffer_tag_start
114    .long 1440   // 宽
115    .long 900   // 高
116    .long 32
117framebuffer_tag_end:
118.align 8
119	.short MULTIBOOT_HEADER_TAG_END
120    // 结束标记
121    .short 0
122    .long 8
123multiboot_header_end:
124
125.section .bootstrap
126
127.global _start
128.type _start, @function
129# 在 multiboot2.cpp 中定义
130
131.extern _start64
132.extern boot_info_addr
133.extern multiboot2_magic
134ENTRY(_start)
135    // 关中断
136    cli
137
138    // multiboot2_info 结构体指针
139    mov %ebx, mb2_info
140    //mov %ebx, %e8
141    // 魔数
142    mov %eax, mb2_magic
143
144    //mov %eax, %e9
145    / 从保护模式跳转到长模式
146    // 1. 允许 PAE
147    mov %cr4, %eax
148    or $(1<<5), %eax
149    mov %eax, %cr4
150    // 2. 设置临时页表
151    // 最高级
152    mov $pml4, %eax
153    mov $pdpt, %ebx
154    or $0x3, %ebx
155    mov %ebx, 0(%eax)
156
157    // 次级
158    mov $pdpt, %eax
159    mov $pd, %ebx
160    or $0x3, %ebx
161    mov %ebx, 0(%eax)
162
163    // 次低级
164    mov $pd, %eax
165    mov $pt, %ebx
166    or $0x3, %ebx
167    mov %ebx, 0(%eax)
168
169    // 最低级
170    // 循环 512 次,填满一页
171    mov $512, %ecx
172    mov $pt, %eax
173    mov $0x3, %ebx
174.fill_pt:
175    mov %ebx, 0(%eax)
176    add $0x1000, %ebx
177    add $8, %eax
178    loop .fill_pt
179
180.global enter_head_from_ap_boot
181enter_head_from_ap_boot:
182    // 填写 CR3
183    mov $pml4, %eax
184    mov %eax, %cr3
185
186    // 3. 切换到 long 模式
187    mov $0xC0000080, %ecx
188    rdmsr
189    or $(1<<8), %eax
190    wrmsr
191
192    // 4. 开启分页
193    mov %cr0, %eax
194    or $(1<<31), %eax
195    mov %eax, %cr0
196
197    // 5. 重新设置 GDT
198    mov $gdt64_pointer, %eax
199    lgdt 0(%eax)
200
201    jmp $0x8, $ready_to_start_64
202    hlt
203    ret
204.code64
205.global ready_to_start_64
206ready_to_start_64:
207
208    mov $0x10, %ax
209    mov %ax, %ds
210    mov %ax, %es
211    mov %ax, %fs
212    mov %ax, %ss
213    mov $0x7e00, %esp
214
215
216    //6. 跳转到start64
217    movq switch_to_start64(%rip), %rax
218    pushq $0x08 //段选择子
219    pushq %rax
220    lretq
221
222switch_to_start64:
223    .quad _start64
224
225
226.code64
227is_from_ap:
228
229    hlt
230
231.global _start64
232.type _start64, @function
233.extern Start_Kernel
234ENTRY(_start64)
235
236
237    // 初始化寄存器
238    mov $0x10, %ax
239    mov %ax, %ds
240    mov %ax, %es
241    mov %ax, %fs
242    mov %ax, %ss
243    mov $0x7e00, %esp
244
245// === 加载GDTR ====
246    lgdt GDT_POINTER(%rip) //这里我没搞明白rip相对寻址, 看了文档,大概是用来实现PIC的(position independent code)
247    //lgdt $GDT_POINTER
248// === 加载IDTR ====
249    lidt IDT_POINTER(%rip)
250    //lidt $IDT_POINTER
251    movq GDT_POINTER(%rip), %r12
252    movq head_stack_start(%rip), %rsp
253
254    // 分支,判断是否为apu
255    movq	$0x1b,	%rcx		// 根据IA32_APIC_BASE.BSP[8]标志位判断处理器是否为apu
256	rdmsr
257	bt	$8,	%rax
258	jnc	load_apu_cr3
259
260    // 2. 设置临时页表
261    // 最高级
262    mov $__PML4E, %eax
263    mov $__PDPTE, %ebx
264    or $0x3, %ebx
265    mov %ebx, 0(%eax)
266
267    mov $__PML4E, %eax
268    // 加256个表项, 映射高地址
269    add $2048, %eax
270    mov %ebx, 0(%eax)
271
272    // 次级
273    mov $__PDPTE, %eax
274    mov $__PDE, %ebx
275    or $0x3, %ebx
276    mov %ebx, 0(%eax)
277
278    // 次低级
279    mov $__PDE, %eax
280    mov $50, %ecx
281    mov $__PT_S, %ebx
282    or $0x3, %ebx
283.fill_pde_64:
284    mov %ebx, 0(%eax)
285    add $0x1000, %ebx
286    add $8, %eax
287    loop .fill_pde_64
288
289    // 最低级
290    // 循环 512*25=12800 次,填满25页,共50M
291    mov $12800, %ecx
292    mov $__PT_S, %eax
293    mov $0x3, %ebx
294.fill_pt_64:
295    mov %ebx, 0(%eax)
296    add $0x1000, %ebx
297    add $8, %eax
298    loop .fill_pt_64
299
300    // 50-100M填0,共25个页表
301    mov $12800, %ecx
302.fill_pt_64_2:
303    movq $0, 0(%eax)
304    add $8, %eax
305    loop .fill_pt_64_2
306
307
308
309// ==== 加载CR3寄存器
310
311load_cr3:
312
313    movq $__PML4E, %rax //设置页目录基地址
314
315    movq %rax, %cr3
316    jmp to_switch_seg
317
318load_apu_cr3:
319    // 由于内存管理模块重置了页表,因此ap核心初始化的时候,需要使用新的内核页表。
320    // 这个页表的值由smp模块设置到__APU_START_CR3变量中
321
322    // 加载__APU_START_CR3中的值
323    movq $__APU_START_CR3, %rax
324    movq 0(%rax), %rax
325    movq %rax, %cr3
326    jmp to_switch_seg
327
328to_switch_seg:
329
330    movq switch_seg(%rip), %rax
331    // 由于ljmp和lcall在GAS中不受支持,因此我们需要先伪造函数调用现场,通过lret的方式,给它跳转过去。才能更新cs寄存器
332    // 实在是太妙了!Amazing!
333    pushq $0x08 //段选择子
334    pushq %rax
335    lretq
336
337// 64位模式的代码
338switch_seg:
339
340    .quad entry64
341
342
343entry64:
344
345    movq $0x10, %rax
346    movq %rax, %ds
347    movq %rax, %es
348    movq %rax, %gs
349    movq %rax, %ss
350
351    movq head_stack_start(%rip), %rsp //rsp的地址
352
353    // 重新加载GDT和IDT,加载到高地址
354    leaq GDT_Table(%rip), %r8
355    leaq GDT_END(%rip), %r9
356
357    subq %r8, %r9
358    movq %r9, %r13    // GDT size
359
360    leaq IDT_Table(%rip), %r8
361    leaq IDT_END(%rip), %r9
362
363    subq %r8, %r9
364    movq %r9, %r12    // IDT size
365
366    lgdt GDT_POINTER64(%rip)
367    lidt IDT_POINTER64(%rip)
368
369    // 分支,判断是否为apu
370    movq	$0x1b,	%rcx		// 根据IA32_APIC_BASE.BSP[8]标志位判断处理器是否为apu
371	rdmsr
372	bt	$8,	%rax
373	jnc	start_smp
374
375setup_IDT:
376    // 该部分代码只在启动初期使用,后面的c文件中会重新设置IDT,
377    leaq m_ignore_int(%rip),  %rdx // 将ignore_int的地址暂时存到中段描述符的高8B
378    movq $(0x08 << 16), %rax  // 设置段选择子。由IDT结构和段选择子结构可知,本行设置段基地址为0x100000,TI=0,RPL=0
379    movw %dx, %ax
380
381    movq $ (0x8e00 << 32), %rcx // 设置Type=1110 P=1 DPL=00 0=0
382    addq %rcx, %rax
383
384    // 把ignore_int的地址填写到正确位置, rax存低8B, rdx存高8B
385    movl %edx, %ecx
386    shrl $16, %ecx // 去除低16位
387    shlq $48, %rcx
388    addq %rcx, %rax // 填写段内偏移31:16
389
390    shrq $32, %rdx // (已经填写了32位,故右移32)
391
392    leaq IDT_Table(%rip), %rdi // 获取中断描述符表的首地址,存储到rdi
393    mov $256, %rcx  // 初始化每个中断描述符
394
395repeat_set_idt:
396    // ====== 循环,初始化总共256个中断描述符 ===
397    movq %rax, (%rdi)   // 保存低8B
398    movq %rdx, 8(%rdi)  // 保存高8B
399
400    addq $0x10, %rdi // 转到下一个IDT表项
401    dec %rcx
402    jne repeat_set_idt
403
404
405    //now enable SSE and the like
406    movq %cr0, %rax
407    and $0xFFFB, %ax		//clear coprocessor emulation CR0.EM
408    or $0x2, %ax			//set coprocessor monitoring  CR0.MP
409    movq %rax, %cr0
410    movq %cr4, %rax
411    or $(3 << 9), %ax		//set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
412    movq %rax, %cr4
413
414
415    movq	go_to_kernel(%rip),	%rax		/* movq address */
416	pushq	$0x08
417	pushq	%rax
418
419
420
421    movq mb2_info, %r15
422    movq mb2_magic, %r14
423
424
425	lretq
426
427go_to_kernel:
428    .quad Start_Kernel
429
430start_smp:
431
432
433    //now enable SSE and the like
434    movq %cr0, %rax
435    and $0xFFFB, %ax		//clear coprocessor emulation CR0.EM
436    or $0x2, %ax			//set coprocessor monitoring  CR0.MP
437    movq %rax, %cr0
438    movq %cr4, %rax
439    or $(3 << 9), %ax		//set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
440    movq %rax, %cr4
441
442
443	movq	go_to_smp_kernel(%rip),	%rax		/* movq address */
444	pushq	$0x08
445	pushq	%rax
446
447/*
448    // 重新加载GDT和IDT,加载到高地址
449    leaq GDT_Table(%rip), %r8
450    leaq GDT_END(%rip), %r9
451
452    subq %r8, %r9
453    movq %r9, %r13    // GDT size
454
455    leaq IDT_Table(%rip), %r8
456    leaq IDT_END(%rip), %r9
457
458    subq %r8, %r9
459    movq %r9, %r12    // IDT size
460
461    lgdt GDT_POINTER64(%rip)
462    lidt IDT_POINTER64(%rip)
463*/
464	lretq
465
466go_to_smp_kernel:
467
468	.quad	smp_ap_start
469
470// ==== 异常/中断处理模块 ignore int: 忽略中断
471// (该部分代码只在启动初期使用,后面的c文件中会重新设置IDT,从而重设ignore_int的中断入点)
472m_ignore_int:
473// 切换到c语言的ignore_int
474    movq go_to_ignore_int(%rip), %rax
475    pushq $0x08
476    pushq %rax
477    lretq
478
479
480
481go_to_ignore_int:
482    .quad ignore_int_handler
483
484ENTRY(head_stack_start)
485    .quad BSP_IDLE_STACK_SPACE + 32768
486
487// 初始化页表
488.align 0x1000 //设置为4k对齐
489__PML4E:
490    .skip 0x1000
491__PDPTE:
492	.skip 0x1000
493
494// 三级页表
495__PDE:
496    .skip 0x1000
497
498// 预留50个四级页表,总共表示100M的内存空间。这50个页表占用200KB的空间
499__PT_S:
500    .skip 0x32000
501
502
503.global __APU_START_CR3
504__APU_START_CR3:
505    .quad 0
506
507// GDT表
508
509.align 16
510.global GDT_Table // 使得GDT可以被外部程序引用或者访问
511
512GDT_Table:
513    .quad 0x0000000000000000 // 0 空描述符 0x00
514    .quad 0x0020980000000000 // 1 内核64位代码段描述符 0x08
515    .quad 0x0000920000000000 // 2 内核64位数据段描述符 0x10
516    .quad 0x0000000000000000 // 3 用户32位代码段描述符 0x18
517    .quad 0x0000000000000000 // 4 用户32位数据段描述符 0x20
518    .quad 0x00cff3000000ffff // 5 用户64位数据段描述符 0x28
519    .quad 0x00affb000000ffff // 6 用户64位代码段描述符 0x30
520    .quad 0x00cf9a000000ffff // 7 内核32位代码段描述符 0x38
521    .quad 0x00cf92000000ffff // 8 内核32位数据段描述符 0x40
522    .fill 100, 8, 0           // 10-11 TSS(跳过了第9段)  重复十次填充8字节的空间,赋值为0   长模式下,每个TSS长度为128bit
523GDT_END:
524
525.global GDT_POINTER
526GDT_POINTER:
527GDT_LIMIT: .word GDT_END - GDT_Table - 1 // GDT的大小
528GDT_BASE: .quad GDT_Table
529
530.global GDT_POINTER64
531GDT_POINTER64:
532GDT_LIMIT64: .word GDT_END - GDT_Table - 1 // GDT的大小
533GDT_BASE64: .quad GDT_Table + 0xffff800000000000
534
535// IDT 表
536.global IDT_Table
537
538IDT_Table:
539    .fill 512, 8, 0 // 设置512*8字节的IDT表的空间
540IDT_END:
541
542.global IDT_POINTER
543IDT_POINTER:
544IDT_LIMIT: .word IDT_END - IDT_Table - 1
545IDT_BASE: .quad IDT_Table
546
547.global IDT_POINTER64
548IDT_POINTER64:
549IDT_LIMIT64: .word IDT_END - IDT_Table - 1
550IDT_BASE64: .quad IDT_Table + 0xffff800000000000
551
552
553
554.section .bootstrap.data
555mb2_magic: .quad 0
556mb2_info: .quad 0
557
558.code32
559// 临时页表 4KB/页
560.align 0x1000
561.global pml4
562pml4:
563    .skip 0x1000
564pdpt:
565    .skip 0x1000
566pd:
567    .skip 0x1000
568pt:
569    .skip 0x1000
570
571// 临时 GDT
572.align 16
573gdt64:
574null_desc:
575    .short 0xFFFF
576    .short 0
577    .byte 0
578    .byte 0
579    .byte 0
580    .byte 0
581code_desc:
582    .short 0
583    .short 0
584    .byte 0
585    .byte 0x9A
586    .byte 0x20
587    .byte 0
588data_desc:
589    .short 0
590    .short 0
591    .byte 0
592    .byte 0x92
593    .byte 0
594    .byte 0
595user_code_desc:
596    .short 0
597    .short 0
598    .byte 0
599    .byte 0xFA
600    .byte 0x20
601    .byte 0
602user_data_desc:
603    .short 0
604    .short 0
605    .byte 0
606    .byte 0xF2
607    .byte 0
608    .byte 0
609gdt64_pointer:
610    .short gdt64_pointer-gdt64-1
611    .quad gdt64
612gdt64_pointer64:
613    .short gdt64_pointer-gdt64-1
614    .quad gdt64
615