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