---
# System prepended metadata

title: Setting up long mode

---

# Setting up long mode
基於 david942j 大大的建議，做完 real mode 先閱讀一下 osdev.wiki 的 [setting up long mode](https://wiki.osdev.org/Setting_Up_Long_Mode)

## Introduction
x86_64 的出現引進了新的 mode: long mode ， long mode 可以再分為兩個 submode: 32bit, 64bit:
- 64bit: 原先的 eax, ebx......等等擴展成 64bit ，另外增加了 general registers: r8 ~ r15 ，GDT, IDT, paging 都跟 32bit 不同
- 32bit: 在 long mode 下稱為相容模式
> 我猜 32bit mode 應該就是所謂的 Protected mode ?

## Detect long mode
以下僅為我的閱讀筆記，尚未寫過 code ，希望沒出什麼錯QQ

簡述之，要檢測 long mode 必須透過 extend function ，而 extent function 則需透過 cpuid 這個指令來確認能否使用，因此關係圖是:
detect cpuid -> detect extend function -> long mode
1. detect cpuid:
    -    可以由 flags register 的 ID flag 可否反轉得知
    -    簡單講就是保存 flags ，再對 ID flag 做反轉，跟剛剛保存的做比對，確認不一樣即代表 cpuid 是可用的
    ```asm=
    pushfd
    pop eax
    mov ecx, eax
    ;把原先的 flag 保存在 ecx 中
    xor eax, 1 << 21
    ;翻轉 id flag
    push eax
    popfd
    ;放回 reg
    pushfd
    pop eax
    ;再給eax
    push ecx
    popfd
    ;存回原先的 flags
    xor ecx, eax
    jz .NoCPUID
    ;比較 + 判斷
    ret
    ```
2. extent function
    -    這比較我看不是很懂...直接看 code 好了
    ```asm=
    mov eax, 0x80000000
    cpuid
    ;所以說經過 cpuid 應該要大於 0x80000001 ?
    cmp eax, 0x80000001
    jb .NoLongMode
    ```
3. long mode
    -    透過 cpuid ，判斷 edx 第 29 bit 是否被設為 1 來確定有無 long mode
    ```x86asm=
    mov eax, 0x80000001
    cpuid
    test edx, 1 << 29
    jz .NoLongMode
    ```
## Setting up the page
原本的文章還有關於如何關掉 protected mode 的 paging ，不過我之前沒有進入 protected mode 所以省略
> 有想過進入 long mode 前是否一定要先從 real mode 進入 protected mode ，但是從後面的文章來看應該是不用......吧

long mode 的 paging 模式是用 PAE (Physical Address Extension) 來擴展，具體會是 4 level 的 paging:
P4LMT -> PDPT(Page Directory Pointer Table) -> PDT(Page Directory Table) -> PT(Page Table)

long mode 為 64bit 的 address 但實際最多只能用 48bit (6 bytes) ，而一個 page 為 4k ，我們的 Page transcation 都是由 virtual address 轉換而來:

offset: 12bits (4k == 2 ^ 12)
48 - 12 = 36
36分給4個table，每個可得 9bits ，換句話說，每個 table 最多有 512 個 entries (2^9)

![](https://i.imgur.com/SNUx9uk.png)

> 這邊不太清楚的地方是那麼多的 table 究竟是真實存在還是必要時才存在，否則 memory 也存太多 table 了吧...
> 基於 virtual 轉成 physical 是縮小範圍，應該不是真的存在那麼多空間

接下來打算讓 PT 為 0x200000 大小，起始地址為 0x1000 ，下一個 0x1000 空間為下一層的 table 空間，而建立 table 需要清掉每個 bytes (以下為 P4LMT):
```asm=
mov edi, 0x1000 ;把位在 memory 的 0x1000 index (=base address) 傳給 edi
mov cr3, edi    ;把基底地址傳給 cr3 (special reg)
xor eax, eax
mov ecx, 4096
rep stosd        ;重複清空該空間
mov edi, cr3     ;回存到 edi
```
接著設定每層的 table
```x86asm=
mov [edi], 0x2003 ; P4LMT 第一層要存 PDPT 起始地址，前幾個 bits 當 flag 用
add edi, 0x1000   ; 一個 table 為 0x1000 大小
mov [edi], 0x3003 ; PDPT 第一層要存 PDT 起始地址，其餘同上
add edi, 0x1000   
mov [edi], 0x4003
add edi, 0x1000; PT
```

設置完所有 table 後，要設定 physical memory ，用 loop 來設定每個 entry
```x86asm=
mov ebx, 0x00000003; 同樣前幾個 bits 當 flag 用
mov ecx, 512       ; 有 512 個 entries 要設定
.SetEntry:
mov [edi], ebx
add ebx, 0x1000    ; 下一個 page
add edi, 0x8       ; 下一個 entry
loop .SetEntry
```
聲明將用 PAE 來 paging ，聲明方法是用 cr4 中的 PAE bit:
```x86asm=
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
```
推測無法直接對 cr 系列的 reg 操作，所以需要傳給 general reg 操作完再傳回去

> 後面有提到關於 5 level paging ，不過跟之後的實作無關所以就不紀錄

因為沒進過 protected mode ，所以採用直接由 real mode 進入 long mode 的方法：
1. 設定 EFER MSR 來聲明要用 long mode
```x86=
mov ecx, 0xc0000080
rdmsr
or eax, 1 << 8
wrmsr
```
2. 啟用 paging 和 protected mode
```x86=
mov eax, cr0
or eax, 1 << 31 | 1 << 0
mov cr0, eax
```
**注意！**
此時只是在 compatible mode ，還需進入 long mode 才是真正進入 64bit 的世界

## Enter long mode
操作很簡單， load 一個帶有 64 flag 的 GDT即可，具體可以參考 amd64 architecture programming manual vol. 2
```x86=
GDT64:                           ; Global Descriptor Table (64-bit).
    .Null: equ $ - GDT64         ; The null descriptor.
    dw 0xFFFF                    ; Limit (low).
    dw 0                         ; Base (low).
    db 0                         ; Base (middle)
    db 0                         ; Access.
    db 1                         ; Granularity.
    db 0                         ; Base (high).
    .Code: equ $ - GDT64         ; The code descriptor.
    dw 0                         ; Limit (low).
    dw 0                         ; Base (low).
    db 0                         ; Base (middle)
    db 10011010b                 ; Access (exec/read).
    db 10101111b                 ; Granularity, 64 bits flag, limit19:16.
    db 0                         ; Base (high).
    .Data: equ $ - GDT64         ; The data descriptor.
    dw 0                         ; Limit (low).
    dw 0                         ; Base (low).
    db 0                         ; Base (middle)
    db 10010010b                 ; Access (read/write).
    db 00000000b                 ; Granularity.
    db 0                         ; Base (high).
    .Pointer:                    ; The GDT-pointer.
    dw $ - GDT64 - 1             ; Limit.
    dq GDT64                     ; Base.
```
設定完 table ，再來 long gdt 即可
```x86=
lgdt [GDT64.Pointer]
jmp GDT64.Code:Realm64
```
剩下是後面的 sample ，操作效果是刷新螢幕:
```x86=
; Use 64-bit.
[BITS 64]
 
Realm64:
    cli                           ; Clear the interrupt flag.
    mov ax, GDT64.Data            ; Set the A-register to the data descriptor.
    mov ds, ax                    ; Set the data segment to the A-register.
    mov es, ax                    ; Set the extra segment to the A-register.
    mov fs, ax                    ; Set the F-segment to the A-register.
    mov gs, ax                    ; Set the G-segment to the A-register.
    mov ss, ax                    ; Set the stack segment to the A-register.
    mov edi, 0xB8000              ; Set the destination index to 0xB8000.
    mov rax, 0x1F201F201F201F20   ; Set the A-register to 0x1F201F201F201F20.
    mov ecx, 500                  ; Set the C-register to 500.
    rep stosq                     ; Clear the screen.
    hlt                           ; Halt the processor.
```
因為目前尚未設定 IDT ，所以不能 enable interrupt 
這筆記大概到這邊為止