POWER 上的巢狀 KVM¶
簡介¶
本文件介紹瞭如果 hypervisor 實現了 hypercall,guest 作業系統如何充當 hypervisor 並透過使用 hypercall 執行巢狀 guest。術語 L0、L1 和 L2 用於指代不同的軟體實體。L0 是通常稱為“主機”或“hypervisor”的 hypervisor 模式實體。L1 是直接在 L0 下執行並由 L0 啟動和控制的 guest 虛擬機器。L2 是由充當 hypervisor 的 L1 啟動和控制的 guest 虛擬機器。
現有 API¶
自 2018 年以來,Linux/KVM 一直支援作為 L0 或 L1 的巢狀
添加了 L0 程式碼
commit 8e3f5fc1045dc49fd175b978c5457f5f51e7a2ce
Author: Paul Mackerras <paulus@ozlabs.org>
Date: Mon Oct 8 16:31:03 2018 +1100
KVM: PPC: Book3S HV: Framework and hcall stubs for nested virtualization
添加了 L1 程式碼
commit 360cae313702cdd0b90f82c261a8302fecef030a
Author: Paul Mackerras <paulus@ozlabs.org>
Date: Mon Oct 8 16:31:04 2018 +1100
KVM: PPC: Book3S HV: Nested guest entry via hypercall
此 API 主要使用單個 hcall h_enter_nested()。L1 呼叫此函式以告知 L0 使用給定狀態啟動 L2 vCPU。然後,L0 啟動此 L2 並執行直到達到 L2 退出條件。一旦 L2 退出,L2 的狀態將由 L0 返回給 L1。完整的 L2 vCPU 狀態始終在執行 L2 時從 L1 傳輸到 L1。L0 不保留任何關於 L2 vCPU 的狀態(除了 L0 在 L1 -> L2 進入和 L2 -> L1 退出時的短暫序列中)。
L0 保留的唯一狀態是分割槽表。L1 使用 h_set_partition_table() hcall 註冊它的分割槽表。L0 擁有的關於 L2 的所有其他狀態都是快取狀態(例如影子頁表)。
L1 可以執行任何 L2 或 vCPU,而無需首先通知 L0。它只需使用 h_enter_nested() 啟動 vCPU。L2 和 vCPU 的建立是在呼叫 h_enter_nested() 時隱式完成的。
在本文件中,我們將此現有 API 稱為 v1 API。
新的 PAPR API¶
新的 PAPR API 更改了 v1 API,使得建立 L2 和關聯的 vCPU 是顯式的。在本文件中,我們將其稱為 v2 API。
h_enter_nested() 被 H_GUEST_VCPU_RUN() 替換。在呼叫此函式之前,L1 必須使用 h_guest_create() 顯式建立 L2,並使用 h_guest_create_vCPU() 建立任何關聯的 vCPU()。可以使用 h_guest_{g|s}et hcall 執行獲取和設定 vCPU 狀態的操作。
L1 建立 L2、執行它並刪除它的基本執行流程是
L1 和 L0 使用 H_GUEST_{G,S}ET_CAPABILITIES() 協商功能(通常在 L1 啟動時)。
L1 請求 L0 使用 H_GUEST_CREATE() 建立 L2 並接收一個令牌
L1 請求 L0 使用 H_GUEST_CREATE_VCPU() 建立一個 L2 vCPU
L1 和 L0 使用 H_GUEST_{G,S}ET() hcall 傳遞 vCPU 狀態
L1 請求 L0 執行執行 H_GUEST_VCPU_RUN() hcall 的 vCPU
L1 使用 H_GUEST_DELETE() 刪除 L2
以下是各個 hcall 的更多詳細資訊
HCALL 詳細資訊¶
提供此文件是為了讓您全面瞭解 API。它並不旨在提供實現 L1 或 L0 所需的所有詳細資訊。有關更多詳細資訊,請參閱最新版本的 PAPR。
所有這些 HCALL 都是由 L1 傳送給 L0 的。
H_GUEST_GET_CAPABILITIES()¶
呼叫此函式以獲取 L0 巢狀 hypervisor 的功能。這包括諸如支援作為 L2 的 CPU 版本(例如 POWER9、POWER10)等功能
H_GUEST_GET_CAPABILITIES(uint64 flags)
Parameters:
Input:
flags: Reserved
Output:
R3: Return code
R4: Hypervisor Supported Capabilities bitmap 1
H_GUEST_SET_CAPABILITIES()¶
呼叫此函式以告知 L0 L1 hypervisor 的功能。此處傳遞的標誌集與 H_GUEST_GET_CAPABILITIES() 相同
通常,將首先呼叫 GET,然後呼叫 SET,其中包含從 GET 返回的標誌的子集。此過程允許 L0 和 L1 協商一組商定的功能
H_GUEST_SET_CAPABILITIES(uint64 flags,
uint64 capabilitiesBitmap1)
Parameters:
Input:
flags: Reserved
capabilitiesBitmap1: Only capabilities advertised through
H_GUEST_GET_CAPABILITIES
Output:
R3: Return code
R4: If R3 = H_P2: The number of invalid bitmaps
R5: If R3 = H_P2: The index of first invalid bitmap
H_GUEST_CREATE()¶
呼叫此函式以建立 L2。將返回已建立的 L2 的唯一 ID(類似於 LPID),該 ID 可用於後續 HCALL 上以標識 L2
H_GUEST_CREATE(uint64 flags,
uint64 continueToken);
Parameters:
Input:
flags: Reserved
continueToken: Initial call set to -1. Subsequent calls,
after H_Busy or H_LongBusyOrder has been
returned, value that was returned in R4.
Output:
R3: Return code. Notable:
H_Not_Enough_Resources: Unable to create Guest VCPU due to not
enough Hypervisor memory. See H_GUEST_CREATE_GET_STATE(flags =
takeOwnershipOfVcpuState)
R4: If R3 = H_Busy or_H_LongBusyOrder -> continueToken
H_GUEST_CREATE_VCPU()¶
呼叫此函式以建立與 L2 關聯的 vCPU。應傳遞 L2 id(從 H_GUEST_CREATE() 返回)。此外,還傳入一個唯一的(對於此 L2)vCPUid。此 vCPUid 由 L1 分配
H_GUEST_CREATE_VCPU(uint64 flags,
uint64 guestId,
uint64 vcpuId);
Parameters:
Input:
flags: Reserved
guestId: ID obtained from H_GUEST_CREATE
vcpuId: ID of the vCPU to be created. This must be within the
range of 0 to 2047
Output:
R3: Return code. Notable:
H_Not_Enough_Resources: Unable to create Guest VCPU due to not
enough Hypervisor memory. See H_GUEST_CREATE_GET_STATE(flags =
takeOwnershipOfVcpuState)
H_GUEST_GET_STATE()¶
呼叫此函式以獲取與 L2 關聯的狀態(guest 範圍或 vCPU 特定)。此資訊透過 Guest State Buffer (GSB) 傳遞,GSB 是一種標準格式,如本文件稍後所述,以下是必要的詳細資訊
這可以獲取 L2 範圍或 vCPU 特定資訊。L2 範圍的示例是時基偏移或程序範圍的頁表資訊。vCPU 特定的示例是 GPR 或 VSR。flags 引數中的一位指定此呼叫是 L2 範圍還是 vCPU 特定的,GSB 中的 ID 必須與此匹配。
L1 提供指向 GSB 的指標作為此呼叫的引數。還提供了與要設定的狀態關聯的 L2 和 vCPU ID。
L1 僅寫入 GSB 中的 ID 和大小。L0 寫入 GSB 中每個 ID 的關聯值
H_GUEST_GET_STATE(uint64 flags,
uint64 guestId,
uint64 vcpuId,
uint64 dataBuffer,
uint64 dataBufferSizeInBytes);
Parameters:
Input:
flags:
Bit 0: getGuestWideState: Request state of the Guest instead
of an individual VCPU.
Bit 1: getHostWideState: Request stats of the Host. This causes
the guestId and vcpuId parameters to be ignored and attempting
to get the VCPU/Guest state will cause an error.
Bits 2-63: Reserved
guestId: ID obtained from H_GUEST_CREATE
vcpuId: ID of the vCPU pass to H_GUEST_CREATE_VCPU
dataBuffer: A L1 real address of the GSB.
If takeOwnershipOfVcpuState, size must be at least the size
returned by ID=0x0001
dataBufferSizeInBytes: Size of dataBuffer
Output:
R3: Return code
R4: If R3 = H_Invalid_Element_Id: The array index of the bad
element ID.
If R3 = H_Invalid_Element_Size: The array index of the bad
element size.
If R3 = H_Invalid_Element_Value: The array index of the bad
element value.
H_GUEST_SET_STATE()¶
呼叫此函式以設定 L2 範圍或 vCPU 特定的 L2 狀態。此資訊透過 Guest State Buffer (GSB) 傳遞,以下是必要的詳細資訊
這可以設定 L2 範圍或 vCPU 特定資訊。L2 範圍的示例是時基偏移或程序範圍的頁表資訊。vCPU 特定的示例是 GPR 或 VSR。flags 引數中的一位指定此呼叫是 L2 範圍還是 vCPU 特定的,GSB 中的 ID 必須與此匹配。
L1 提供指向 GSB 的指標作為此呼叫的引數。還提供了與要設定的狀態關聯的 L2 和 vCPU ID。
L1 寫入 GSB 中的所有值,L0 僅讀取此呼叫的 GSB
H_GUEST_SET_STATE(uint64 flags,
uint64 guestId,
uint64 vcpuId,
uint64 dataBuffer,
uint64 dataBufferSizeInBytes);
Parameters:
Input:
flags:
Bit 0: getGuestWideState: Request state of the Guest instead
of an individual VCPU.
Bit 1: returnOwnershipOfVcpuState Return Guest VCPU state. See
GET_STATE takeOwnershipOfVcpuState
Bits 2-63: Reserved
guestId: ID obtained from H_GUEST_CREATE
vcpuId: ID of the vCPU pass to H_GUEST_CREATE_VCPU
dataBuffer: A L1 real address of the GSB.
If takeOwnershipOfVcpuState, size must be at least the size
returned by ID=0x0001
dataBufferSizeInBytes: Size of dataBuffer
Output:
R3: Return code
R4: If R3 = H_Invalid_Element_Id: The array index of the bad
element ID.
If R3 = H_Invalid_Element_Size: The array index of the bad
element size.
If R3 = H_Invalid_Element_Value: The array index of the bad
element value.
H_GUEST_RUN_VCPU()¶
呼叫此函式以執行 L2 vCPU。L2 和 vCPU ID 作為引數傳入。vCPU 使用先前使用 H_GUEST_SET_STATE() 設定的狀態執行。當 L2 退出時,L1 將從此 hcall 恢復。
此 hcall 還具有關聯的輸入和輸出 GSB。與 H_GUEST_{S,G}ET_STATE() 不同,這些 GSB 指標不是作為引數傳遞給 hcall 的(這樣做是為了提高效能)。必須使用 ID 0x0c00 和 0x0c01 透過 H_GUEST_SET_STATE() 呼叫預先註冊這些 GSB 的位置(參見下表)。
輸入 GSB 可能僅包含要設定的 VCPU 特定元素。如果不需要設定任何內容,此 GSB 也可能包含零元素(即 GSB 的前 4 個位元組為 0)。
在 hcall 退出時,輸出緩衝區將填充 L0 確定的元素。退出原因是包含在 GPR4 中的(即 NIP 放在 GPR4 中)。返回的元素取決於退出型別。例如,如果退出原因是 L2 執行 hcall(GPR4 = 0xc00),則 GPR3-12 在輸出 GSB 中提供,因為這可能是服務 hcall 所需的狀態。如果需要其他狀態,則 L1 可能會呼叫 H_GUEST_GET_STATE()。
為了在 L2 中合成中斷,在呼叫 H_GUEST_RUN_VCPU() 時,L1 可能會設定一個標誌(作為 hcall 引數),並且 L0 將在 L2 中合成中斷。或者,L1 可能會使用 H_GUEST_SET_STATE() 或 H_GUEST_RUN_VCPU() 輸入 GSB 來設定適當的狀態,從而自行合成中斷
H_GUEST_RUN_VCPU(uint64 flags,
uint64 guestId,
uint64 vcpuId,
uint64 dataBuffer,
uint64 dataBufferSizeInBytes);
Parameters:
Input:
flags:
Bit 0: generateExternalInterrupt: Generate an external interrupt
Bit 1: generatePrivilegedDoorbell: Generate a Privileged Doorbell
Bit 2: sendToSystemReset”: Generate a System Reset Interrupt
Bits 3-63: Reserved
guestId: ID obtained from H_GUEST_CREATE
vcpuId: ID of the vCPU pass to H_GUEST_CREATE_VCPU
Output:
R3: Return code
R4: If R3 = H_Success: The reason L1 VCPU exited (ie. NIA)
0x000: The VCPU stopped running for an unspecified reason. An
example of this is the Hypervisor stopping a VCPU running
due to an outstanding interrupt for the Host Partition.
0x980: HDEC
0xC00: HCALL
0xE00: HDSI
0xE20: HISI
0xE40: HEA
0xF80: HV Fac Unavail
If R3 = H_Invalid_Element_Id, H_Invalid_Element_Size, or
H_Invalid_Element_Value: R4 is offset of the invalid element
in the input buffer.
H_GUEST_DELETE()¶
呼叫此函式以刪除 L2。所有關聯的 vCPU 也將被刪除。不提供特定的 vCPU 刪除呼叫。
可以提供一個標誌來刪除所有 guest。在 kdump/kexec 的情況下,這用於重置 L0
H_GUEST_DELETE(uint64 flags,
uint64 guestId)
Parameters:
Input:
flags:
Bit 0: deleteAllGuests: deletes all guests
Bits 1-63: Reserved
guestId: ID obtained from H_GUEST_CREATE
Output:
R3: Return code
Guest 狀態緩衝區¶
Guest State Buffer (GSB) 是透過 H_GUEST_{G,S}ET() 和 H_GUEST_VCPU_RUN() 呼叫在 L1 和 L0 之間傳遞關於 L2 狀態的主要方法。
狀態可能與整個 L2(例如時基偏移)或特定的 L2 vCPU(例如 GPR 狀態)關聯。只有 L2 VCPU 狀態才能透過 H_GUEST_VCPU_RUN() 設定。
GSB 中的所有資料都是大端位元組序(這是 PAPR 中的標準)。
Guest 狀態緩衝區具有一個標頭,該標頭給出了元素的數量,後跟 GSB 元素本身。
GSB 標頭
偏移位元組 |
大小位元組 |
目的 |
|---|---|---|
0 |
4 |
元素數量 |
4 |
Guest 狀態緩衝區元素 |
GSB 元素
偏移位元組 |
大小位元組 |
目的 |
|---|---|---|
0 |
2 |
ID |
2 |
2 |
值的大小 |
4 |
如上 |
值 |
GSB 元素中的 ID 指定要設定的內容。這包括諸如 GPR、VSR、SPR 等架構狀態,以及諸如時基偏移和分割槽範圍頁表資訊等關於分割槽的一些元資料。
ID |
大小位元組 |
RW |
(H)ost (G)uest (T)hread 範圍 |
詳細資訊 |
|---|---|---|---|---|
0x0000 |
RW |
TG |
NOP 元素 |
|
0x0001 |
0x08 |
R |
G |
L0 vCPU 狀態的大小。參見:H_GUEST_GET_STATE:flags = takeOwnershipOfVcpuState |
0x0002 |
0x08 |
R |
G |
執行 vCPU 輸出緩衝區的大小 |
0x0003 |
0x04 |
RW |
G |
邏輯 PVR |
0x0004 |
0x08 |
RW |
G |
TB 偏移(L1 相對) |
0x0005 |
0x18 |
RW |
G |
分割槽範圍頁表資訊
|
0x0006 |
0x10 |
RW |
G |
程序表資訊
|
0x0007- 0x07FF |
保留 |
|||
0x0800 |
0x08 |
R |
H |
L0 的 Guest 管理空間中用於 L1-Lpar 的當前使用量(以位元組為單位)。 |
0x0801 |
0x08 |
R |
H |
L0 的 Guest 管理空間中用於 L1-Lpar 的最大可用位元組數 |
0x0802 |
0x08 |
R |
H |
L0 的 Guest 頁表管理空間中用於 L1-Lpar 的當前使用量(以位元組為單位) |
0x0803 |
0x08 |
R |
H |
L0 的 Guest 頁表管理空間中用於 L1-Lpar 的最大可用位元組數 |
0x0804 |
0x08 |
R |
H |
由於過度提交,從 L0 Guest 的頁表管理空間回收的累積位元組數 |
0x0805- 0x0BFF |
保留 |
|||
0x0C00 |
0x10 |
RW |
T |
執行 vCPU 輸入緩衝區
|
0x0C01 |
0x10 |
RW |
T |
執行 vCPU 輸出緩衝區
|
0x0C02 |
0x08 |
RW |
T |
vCPU VPA 地址 |
0x0C03- 0x0FFF |
保留 |
|||
0x1000- 0x101F |
0x08 |
RW |
T |
GPR 0-31 |
0x1020 |
0x08 |
T |
T |
HDEC 過期 TB |
0x1021 |
0x08 |
RW |
T |
NIA |
0x1022 |
0x08 |
RW |
T |
MSR |
0x1023 |
0x08 |
RW |
T |
LR |
0x1024 |
0x08 |
RW |
T |
XER |
0x1025 |
0x08 |
RW |
T |
CTR |
0x1026 |
0x08 |
RW |
T |
CFAR |
0x1027 |
0x08 |
RW |
T |
SRR0 |
0x1028 |
0x08 |
RW |
T |
SRR1 |
0x1029 |
0x08 |
RW |
T |
DAR |
0x102A |
0x08 |
RW |
T |
DEC 過期 TB |
0x102B |
0x08 |
RW |
T |
VTB |
0x102C |
0x08 |
RW |
T |
LPCR |
0x102D |
0x08 |
RW |
T |
HFSCR |
0x102E |
0x08 |
RW |
T |
FSCR |
0x102F |
0x08 |
RW |
T |
FPSCR |
0x1030 |
0x08 |
RW |
T |
DAWR0 |
0x1031 |
0x08 |
RW |
T |
DAWR1 |
0x1032 |
0x08 |
RW |
T |
CIABR |
0x1033 |
0x08 |
RW |
T |
PURR |
0x1034 |
0x08 |
RW |
T |
SPURR |
0x1035 |
0x08 |
RW |
T |
IC |
0x1036- 0x1039 |
0x08 |
RW |
T |
SPRG 0-3 |
0x103A |
0x08 |
W |
T |
PPR |
0x103B 0x103E |
0x08 |
RW |
T |
MMCR 0-3 |
0x103F |
0x08 |
RW |
T |
MMCRA |
0x1040 |
0x08 |
RW |
T |
SIER |
0x1041 |
0x08 |
RW |
T |
SIER 2 |
0x1042 |
0x08 |
RW |
T |
SIER 3 |
0x1043 |
0x08 |
RW |
T |
BESCR |
0x1044 |
0x08 |
RW |
T |
EBBHR |
0x1045 |
0x08 |
RW |
T |
EBBRR |
0x1046 |
0x08 |
RW |
T |
AMR |
0x1047 |
0x08 |
RW |
T |
IAMR |
0x1048 |
0x08 |
RW |
T |
AMOR |
0x1049 |
0x08 |
RW |
T |
UAMOR |
0x104A |
0x08 |
RW |
T |
SDAR |
0x104B |
0x08 |
RW |
T |
SIAR |
0x104C |
0x08 |
RW |
T |
DSCR |
0x104D |
0x08 |
RW |
T |
TAR |
0x104E |
0x08 |
RW |
T |
DEXCR |
0x104F |
0x08 |
RW |
T |
HDEXCR |
0x1050 |
0x08 |
RW |
T |
HASHKEYR |
0x1051 |
0x08 |
RW |
T |
HASHPKEYR |
0x1052 |
0x08 |
RW |
T |
CTRL |
0x1053 |
0x08 |
RW |
T |
DPDES |
0x1054- 0x1FFF |
保留 |
|||
0x2000 |
0x04 |
RW |
T |
CR |
0x2001 |
0x04 |
RW |
T |
PIDR |
0x2002 |
0x04 |
RW |
T |
DSISR |
0x2003 |
0x04 |
RW |
T |
VSCR |
0x2004 |
0x04 |
RW |
T |
VRSAVE |
0x2005 |
0x04 |
RW |
T |
DAWRX0 |
0x2006 |
0x04 |
RW |
T |
DAWRX1 |
0x2007- 0x200c |
0x04 |
RW |
T |
PMC 1-6 |
0x200D |
0x04 |
RW |
T |
WORT |
0x200E |
0x04 |
RW |
T |
PSPB |
0x200F- 0x2FFF |
保留 |
|||
0x3000- 0x303F |
0x10 |
RW |
T |
VSR 0-63 |
0x3040- 0xEFFF |
保留 |
|||
0xF000 |
0x08 |
R |
T |
HDAR |
0xF001 |
0x04 |
R |
T |
HDSISR |
0xF002 |
0x04 |
R |
T |
HEIR |
0xF003 |
0x08 |
R |
T |
ASDR |
其他資訊¶
不在 ptregs/hvregs 中的狀態¶
在 v1 API 中,某些狀態不在 ptregs/hvstate 中。這包括向量暫存器和一些 SPR。為了讓 L1 為 L2 設定此狀態,L1 在 h_enter_nested() 呼叫之前載入這些硬體暫存器,並且 L0 確保它們最終成為 L2 狀態(透過不觸控它們)。
v2 API 刪除了此功能,並透過 GSB 顯式設定此狀態。
L1 實現細節:快取狀態¶
在 v1 API 中,所有狀態都在每次 h_enter_nested() hcall 上從 L1 傳送到 L0,反之亦然。如果 L0 當前未執行任何 L2,則 L0 沒有關於它們的狀態資訊。唯一的例外是分割槽表的位置,該位置透過 h_set_partition_table() 註冊。
v2 API 更改了此功能,以便 L0 即使在其 vCPU 不再執行時也保留 L2 狀態。這意味著 L1 僅需要在需要修改 L2 狀態時或其值已過時時才需要與 L0 通訊關於 L2 狀態。這為效能最佳化提供了機會。
當 vCPU 從 H_GUEST_RUN_VCPU() 呼叫退出時,L1 在內部將所有 L2 狀態標記為無效。這意味著如果 L1 想要知道 L2 狀態(例如透過 kvm_get_one_reg() 呼叫),則需要呼叫 H_GUEST_GET_STATE() 來獲取該狀態。一旦讀取,它將在 L1 中標記為有效,直到再次執行 L2。
此外,當 L1 修改 L2 vcpu 狀態時,在再次執行該 L2 vcpu 之前,它不需要將其寫入 L0。因此,當 L1 更新狀態時(例如透過 kvm_set_one_reg() 呼叫),它會寫入內部 L1 副本,並且僅當 L2 再次透過 H_GUEST_VCPU_RUN() 輸入緩衝區執行時才將此副本重新整理到 L0。
L1 透過這種延遲更新狀態的方式避免了不必要的 H_GUEST_{G|S}ET_STATE() 呼叫。