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