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