# 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)

> 這邊不太清楚的地方是那麼多的 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
這筆記大概到這邊為止