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

分割槽範圍頁表資訊

  • 0x00 Addr part 範圍表

  • 0x08 地址位數

  • 0x10 根目錄大小

0x0006

0x10

RW

G

程序表資訊

  • 0x0 地址程序範圍表

  • 0x8 表大小。

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 輸入緩衝區

  • 0x0 緩衝區地址

  • 0x8 緩衝區大小。

0x0C01

0x10

RW

T

執行 vCPU 輸出緩衝區

  • 0x0 緩衝區地址

  • 0x8 緩衝區大小。

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() 呼叫。