From: Michael Roth <hidden> Date: 2023-12-30 16:28:45
This patchset is also available at:
https://github.com/amdese/linux/commits/snp-host-init-v1
and is based on top of linux-next tag next-20231222
These patches were originally included in v10 of the SNP KVM/hypervisor
patches[1], but have been split off from the general KVM support for easier
review and eventual merging into the x86 tree. They are based on linux-next
to help stay in sync with both tip and kvm-next.
There is 1 KVM-specific patch here since it is needed to avoid regressions
when running legacy SEV guests while the RMP table is enabled.
== OVERVIEW ==
AMD EPYC systems utilizing Zen 3 and newer microarchitectures add support
for a new feature called SEV-SNP, which adds Secure Nested Paging support
on top of the SEV/SEV-ES support already present on existing EPYC systems.
One of the main features of SNP is the addition of an RMP (Reverse Map)
table to enforce additional security protections for private guest memory.
This series primarily focuses on the various host initialization
requirements for enabling SNP on the system, while the actual KVM support
for running SNP guests is added as a separate series based on top of these
patches.
The basic requirements to initialize SNP support on a host when the feature
has been enabled in the BIOS are:
- Discovering and initializing the RMP table
- Initializing various MSRs to enable the capability across CPUs
- Various tasks to maintain legacy functionality on the system, such as:
- Setting up hooks for handling RMP-related changes for IOMMU pages
- Initializing SNP in the firmware via the CCP driver, and implement
additional requirements needed for continued operation of legacy
SEV/SEV-ES guests
Additionally some basic SEV ioctl interfaces are added to configure various
aspects of SNP-enabled firmwares via the CCP driver.
More details are available in the SEV-SNP Firmware ABI[2].
== KNOWN ISSUES ==
- When SNP is enabled, it has been observed that TMR allocation may fail when
reloading CCP driver after kexec. This is being investigated.
Feedback/review is very much appreciated!
-Mike
[1] https://lore.kernel.org/kvm/20231016132819.1002933-1-michael.roth@amd.com/
[2] https://www.amd.com/en/developer/sev.html
Changes since being split off from v10 hypervisor patches:
* Move all host initialization patches to beginning of overall series and
post as a separate patchset. Include only KVM patches that are necessary
for maintaining legacy SVM/SEV functionality with SNP enabled.
(Paolo, Boris)
* Don't enable X86_FEATURE_SEV_SNP until all patches are in place to maintain
legacy SVM/SEV/SEV-ES functionality when RMP table and SNP firmware support
are enabled. (Paolo)
* Re-write how firmware-owned buffers are handled when dealing with legacy
SEV commands. Allocate on-demand rather than relying on pre-allocated pool,
use a descriptor format for handling nested/pointer params instead of
relying on macros. (Boris)
* Don't introduce sev-host.h, re-use sev.h (Boris)
* Various renames, cleanups, refactorings throughout the tree (Boris)
* Rework leaked pages handling (Vlastimil)
* Fix kernel-doc errors introduced by series (Boris)
* Fix warnings when onlining/offlining CPUs (Jeremi, Ashish)
* Ensure X86_FEATURE_SEV_SNP is cleared early enough that AutoIBRS will still
be enabled if RMP table support is not available/configured. (Tom)
* Only read the RMP base/end MSR values once via BSP (Boris)
* Handle IOMMU SNP setup automatically based on state machine rather than via
external caller (Boris)
----------------------------------------------------------------
Ashish Kalra (5):
iommu/amd: Don't rely on external callers to enable IOMMU SNP support
x86/mtrr: Don't print errors if MtrrFixDramModEn is set when SNP enabled
x86/sev: Introduce snp leaked pages list
iommu/amd: Clean up RMP entries for IOMMU pages during SNP shutdown
crypto: ccp: Add panic notifier for SEV/SNP firmware shutdown on kdump
Brijesh Singh (16):
x86/cpufeatures: Add SEV-SNP CPU feature
x86/sev: Add the host SEV-SNP initialization support
x86/sev: Add RMP entry lookup helpers
x86/fault: Add helper for dumping RMP entries
x86/traps: Define RMP violation #PF error code
x86/sev: Add helper functions for RMPUPDATE and PSMASH instruction
x86/sev: Invalidate pages from the direct map when adding them to the RMP table
crypto: ccp: Define the SEV-SNP commands
crypto: ccp: Add support to initialize the AMD-SP for SEV-SNP
crypto: ccp: Provide API to issue SEV and SNP commands
crypto: ccp: Handle the legacy TMR allocation when SNP is enabled
crypto: ccp: Handle legacy SEV commands when SNP is enabled
crypto: ccp: Add debug support for decrypting pages
KVM: SEV: Make AVIC backing, VMSA and VMCB memory allocation SNP safe
crypto: ccp: Add the SNP_PLATFORM_STATUS command
crypto: ccp: Add the SNP_SET_CONFIG command
Kim Phillips (1):
x86/speculation: Do not enable Automatic IBRS if SEV SNP is enabled
Michael Roth (2):
x86/fault: Dump RMP table information when RMP page faults occur
x86/cpufeatures: Enable/unmask SEV-SNP CPU feature
Tom Lendacky (2):
crypto: ccp: Handle non-volatile INIT_EX data when SNP is enabled
crypto: ccp: Add the SNP_COMMIT command
Documentation/virt/coco/sev-guest.rst | 51 ++
arch/x86/Kbuild | 2 +
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/include/asm/disabled-features.h | 8 +-
arch/x86/include/asm/iommu.h | 1 +
arch/x86/include/asm/kvm-x86-ops.h | 1 +
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/include/asm/msr-index.h | 11 +-
arch/x86/include/asm/sev.h | 36 +
arch/x86/include/asm/trap_pf.h | 20 +-
arch/x86/kernel/cpu/amd.c | 20 +-
arch/x86/kernel/cpu/common.c | 7 +-
arch/x86/kernel/cpu/mtrr/generic.c | 3 +
arch/x86/kernel/crash.c | 7 +
arch/x86/kvm/lapic.c | 5 +-
arch/x86/kvm/svm/nested.c | 2 +-
arch/x86/kvm/svm/sev.c | 37 +-
arch/x86/kvm/svm/svm.c | 17 +-
arch/x86/kvm/svm/svm.h | 1 +
arch/x86/mm/fault.c | 5 +
arch/x86/virt/svm/Makefile | 3 +
arch/x86/virt/svm/sev.c | 513 +++++++++++++
drivers/crypto/ccp/sev-dev.c | 1216 +++++++++++++++++++++++++++---
drivers/crypto/ccp/sev-dev.h | 5 +
drivers/iommu/amd/amd_iommu.h | 1 -
drivers/iommu/amd/init.c | 120 ++-
include/linux/amd-iommu.h | 6 +-
include/linux/psp-sev.h | 337 ++++++++-
include/uapi/linux/psp-sev.h | 59 ++
tools/arch/x86/include/asm/cpufeatures.h | 1 +
30 files changed, 2359 insertions(+), 138 deletions(-)
create mode 100644 arch/x86/virt/svm/Makefile
create mode 100644 arch/x86/virt/svm/sev.c
From: Michael Roth <hidden> Date: 2023-12-30 16:20:28
From: Brijesh Singh <redacted>
Add a helper that can be used to access information contained in the RMP
entry corresponding to a particular PFN. This will be needed to make
decisions on how to handle setting up mappings in the NPT in response to
guest page-faults and handling things like cleaning up pages and setting
them back to the default hypervisor-owned state when they are no longer
being used for private data.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: separate 'assigned' indicator from return code, and simplify
function signatures for various helpers]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/sev.h | 3 +++
arch/x86/virt/svm/sev.c | 49 ++++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+)
@@ -53,6 +53,9 @@ struct rmpentry {*/#define RMPTABLE_CPU_BOOKKEEPING_SZ 0x4000+/* Mask to apply to a PFN to get the first PFN of a 2MB page */+#define PFN_PMD_MASK GENMASK_ULL(63, PMD_SHIFT - PAGE_SHIFT)+staticu64probed_rmp_base,probed_rmp_size;staticstructrmpentry*rmptable__ro_after_init;staticu64rmptable_max_pfn__ro_after_init;
@@ -217,3 +220,49 @@ static int __init snp_rmptable_init(void)*ThismustbecalledaftertheIOMMUhasbeeninitialized.*/device_initcall(snp_rmptable_init);++staticstructrmpentry*get_rmpentry(u64pfn)+{+if(WARN_ON_ONCE(pfn>rmptable_max_pfn))+returnERR_PTR(-EFAULT);++return&rmptable[pfn];+}++staticstructrmpentry*__snp_lookup_rmpentry(u64pfn,int*level)+{+structrmpentry*large_entry,*entry;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+returnERR_PTR(-ENODEV);++entry=get_rmpentry(pfn);+if(IS_ERR(entry))+returnentry;++/*+*FindtheauthoritativeRMPentryforaPFN.Thiscanbeeithera4K+*RMPentryoraspeciallargeRMPentrythatisauthoritativefora+*whole2Marea.+*/+large_entry=get_rmpentry(pfn&PFN_PMD_MASK);+if(IS_ERR(large_entry))+returnlarge_entry;++*level=RMP_TO_PG_LEVEL(large_entry->pagesize);++returnentry;+}++intsnp_lookup_rmpentry(u64pfn,bool*assigned,int*level)+{+structrmpentry*e;++e=__snp_lookup_rmpentry(pfn,level);+if(IS_ERR(e))+returnPTR_ERR(e);++*assigned=!!e->assigned;+return0;+}+EXPORT_SYMBOL_GPL(snp_lookup_rmpentry);
From: Michael Roth <hidden> Date: 2023-12-30 16:20:48
From: Brijesh Singh <redacted>
This information will be useful for debugging things like page faults
due to RMP access violations and RMPUPDATE failures.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: move helper to standalone patch, rework dump logic to reduce
verbosity]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/sev.h | 2 +
arch/x86/virt/svm/sev.c | 77 ++++++++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+)
@@ -266,3 +266,80 @@ int snp_lookup_rmpentry(u64 pfn, bool *assigned, int *level)return0;}EXPORT_SYMBOL_GPL(snp_lookup_rmpentry);++/*+*DumptherawRMPentryforaparticularPFN.Thesebitsaredocumentedinthe+*PPRforaparticularCPUmodelandprovideusefulinformationabouthowa+*particularPFNisbeingutilizedbythekernel/firmwareatthetimecertain+*unexpectedeventsoccur,suchasRMPfaults.+*/+staticvoiddump_rmpentry(u64pfn)+{+u64pfn_current,pfn_end;+structrmpentry*e;+u64*e_data;+intlevel;++e=__snp_lookup_rmpentry(pfn,&level);+if(IS_ERR(e)){+pr_info("Failed to read RMP entry for PFN 0x%llx, error %ld\n",+pfn,PTR_ERR(e));+return;+}++e_data=(u64*)e;+if(e->assigned){+pr_info("RMP entry for PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",+pfn,e_data[1],e_data[0]);+return;+}++/*+*IftheRMPentryforaparticularPFNisnotinanassignedstate,+*thenitissometimesusefultogetanideaofwhetherornotanyRMP+*entriesforotherPFNswithinthesame2MBregionareassigned,since+*thosetoocanaffecttheabilitytoaccessaparticularPFNin+*certainsituations,suchaswhenthePFNisbeingaccessedviaa2MB+*mappinginthehostpagetable.+*/+pfn_current=ALIGN(pfn,PTRS_PER_PMD);+pfn_end=pfn_current+PTRS_PER_PMD;++while(pfn_current<pfn_end){+e=__snp_lookup_rmpentry(pfn_current,&level);+if(IS_ERR(e)){+pfn_current++;+continue;+}++e_data=(u64*)e;+if(e_data[0]||e_data[1]){+pr_info("No assigned RMP entry for PFN 0x%llx, but the 2MB region contains populated RMP entries, e.g.: PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",+pfn,pfn_current,e_data[1],e_data[0]);+return;+}+pfn_current++;+}++pr_info("No populated RMP entries in the 2MB region containing PFN 0x%llx\n",+pfn);+}++voidsnp_dump_hva_rmpentry(unsignedlonghva)+{+unsignedintlevel;+pgd_t*pgd;+pte_t*pte;++pgd=__va(read_cr3_pa());+pgd+=pgd_index(hva);+pte=lookup_address_in_pgd(pgd,hva,&level);++if(!pte){+pr_info("Can't dump RMP entry for HVA %lx: no PTE/PFN found\n",hva);+return;+}++dump_rmpentry(pte_pfn(*pte));+}+EXPORT_SYMBOL_GPL(snp_dump_hva_rmpentry);
From: Michael Roth <hidden> Date: 2023-12-30 16:21:30
From: Brijesh Singh <redacted>
Bit 31 in the page fault-error bit will be set when processor encounters
an RMP violation.
While at it, use the BIT() macro.
Signed-off-by: Brijesh Singh <redacted>
Signed-off by: Ashish Kalra [off-list ref]
Acked-by: Dave Hansen <redacted>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/trap_pf.h | 20 ++++++++++++--------
arch/x86/mm/fault.c | 1 +
2 files changed, 13 insertions(+), 8 deletions(-)
From: Michael Roth <hidden> Date: 2023-12-30 16:21:53
RMP faults on kernel addresses are fatal and should never happen in
practice. They indicate a bug in the host kernel somewhere. Userspace
RMP faults shouldn't occur either, since even for VMs the memory used
for private pages is handled by guest_memfd and by design is not
mappable by userspace.
Dump RMP table information about the PFN corresponding to the faulting
HVA to help diagnose any issues of this sort when show_fault_oops() is
triggered by an RMP fault.
Signed-off-by: Michael Roth <redacted>
---
arch/x86/mm/fault.c | 4 ++++
1 file changed, 4 insertions(+)
From: Michael Roth <hidden> Date: 2023-12-30 16:22:14
From: Brijesh Singh <redacted>
The RMPUPDATE instruction writes a new RMP entry in the RMP Table. The
hypervisor will use the instruction to add pages to the RMP table. See
APM3 for details on the instruction operations.
The PSMASH instruction expands a 2MB RMP entry into a corresponding set
of contiguous 4KB RMP entries. The hypervisor will use this instruction
to adjust the RMP entry without invalidating it.
Add helpers to make use of these instructions.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: add RMPUPDATE retry logic for transient FAIL_OVERLAP errors]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/sev.h | 23 +++++++++++
arch/x86/virt/svm/sev.c | 79 ++++++++++++++++++++++++++++++++++++++
2 files changed, 102 insertions(+)
@@ -343,3 +343,82 @@ void snp_dump_hva_rmpentry(unsigned long hva)dump_rmpentry(pte_pfn(*pte));}EXPORT_SYMBOL_GPL(snp_dump_hva_rmpentry);++/*+*PSMASHa2MBalignedpageinto4KpagesintheRMPtablewhilepreservingthe+*Validatedbit.+*/+intpsmash(u64pfn)+{+unsignedlongpaddr=pfn<<PAGE_SHIFT;+intret;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return-ENODEV;++if(!pfn_valid(pfn))+return-EINVAL;++/* Binutils version 2.36 supports the PSMASH mnemonic. */+asmvolatile(".byte 0xF3, 0x0F, 0x01, 0xFF"+:"=a"(ret)+:"a"(paddr)+:"memory","cc");++returnret;+}+EXPORT_SYMBOL_GPL(psmash);++staticintrmpupdate(u64pfn,structrmp_state*state)+{+unsignedlongpaddr=pfn<<PAGE_SHIFT;+intret;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return-ENODEV;++do{+/* Binutils version 2.36 supports the RMPUPDATE mnemonic. */+asmvolatile(".byte 0xF2, 0x0F, 0x01, 0xFE"+:"=a"(ret)+:"a"(paddr),"c"((unsignedlong)state)+:"memory","cc");+}while(ret==RMPUPDATE_FAIL_OVERLAP);++if(ret){+pr_err("RMPUPDATE failed for PFN %llx, ret: %d\n",pfn,ret);+dump_rmpentry(pfn);+dump_stack();+return-EFAULT;+}++return0;+}++/* Transition a page to guest-owned/private state in the RMP table. */+intrmp_make_private(u64pfn,u64gpa,enumpg_levellevel,intasid,boolimmutable)+{+structrmp_statestate;++memset(&state,0,sizeof(state));+state.assigned=1;+state.asid=asid;+state.immutable=immutable;+state.gpa=gpa;+state.pagesize=PG_LEVEL_TO_RMP(level);++returnrmpupdate(pfn,&state);+}+EXPORT_SYMBOL_GPL(rmp_make_private);++/* Transition a page to hypervisor-owned/shared state in the RMP table. */+intrmp_make_shared(u64pfn,enumpg_levellevel)+{+structrmp_statestate;++memset(&state,0,sizeof(state));+state.pagesize=PG_LEVEL_TO_RMP(level);++returnrmpupdate(pfn,&state);+}+EXPORT_SYMBOL_GPL(rmp_make_shared);
From: Michael Roth <hidden> Date: 2023-12-30 16:22:31
From: Brijesh Singh <redacted>
The integrity guarantee of SEV-SNP is enforced through the RMP table.
The RMP is used with standard x86 and IOMMU page tables to enforce
memory restrictions and page access rights. The RMP check is enforced as
soon as SEV-SNP is enabled globally in the system. When hardware
encounters an RMP-check failure, it raises a page-fault exception.
If the kernel uses a 2MB directmap mapping to write to an address, and
that 2MB range happens to contain a 4KB page that set to private in the
RMP table, that will also lead to a page-fault exception.
Prevent this by removing pages from the directmap prior to setting them
as private in the RMP table, and then re-adding them to the directmap
when they set back to shared.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/virt/svm/sev.c | 58 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 56 insertions(+), 2 deletions(-)
@@ -369,14 +369,64 @@ int psmash(u64 pfn)}EXPORT_SYMBOL_GPL(psmash);+staticintrestore_direct_map(u64pfn,intnpages)+{+inti,ret=0;++for(i=0;i<npages;i++){+ret=set_direct_map_default_noflush(pfn_to_page(pfn+i));+if(ret)+break;+}++if(ret)+pr_warn("Failed to restore direct map for pfn 0x%llx, ret: %d\n",+pfn+i,ret);++returnret;+}++staticintinvalidate_direct_map(u64pfn,intnpages)+{+inti,ret=0;++for(i=0;i<npages;i++){+ret=set_direct_map_invalid_noflush(pfn_to_page(pfn+i));+if(ret)+break;+}++if(ret){+pr_warn("Failed to invalidate direct map for pfn 0x%llx, ret: %d\n",+pfn+i,ret);+restore_direct_map(pfn,i);+}++returnret;+}+staticintrmpupdate(u64pfn,structrmp_state*state){unsignedlongpaddr=pfn<<PAGE_SHIFT;-intret;+intret,level,npages;if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))return-ENODEV;+level=RMP_TO_PG_LEVEL(state->pagesize);+npages=page_level_size(level)/PAGE_SIZE;++/*+*Ifthekernelusesa2MBdirectmapmappingtowritetoanaddress,+*andthat2MBrangehappenstocontaina4KBpagethatsettoprivate+*intheRMPtable,anRMP#PFwilltriggerandcauseahostcrash.+*+*Preventthisbyremovingpagesfromthedirectmappriortosetting+*themasprivateintheRMPtable.+*/+if(state->assigned&&invalidate_direct_map(pfn,npages))+return-EFAULT;+do{/* Binutils version 2.36 supports the RMPUPDATE mnemonic. */asmvolatile(".byte 0xF2, 0x0F, 0x01, 0xFE"
@@ -386,12 +436,16 @@ static int rmpupdate(u64 pfn, struct rmp_state *state)}while(ret==RMPUPDATE_FAIL_OVERLAP);if(ret){-pr_err("RMPUPDATE failed for PFN %llx, ret: %d\n",pfn,ret);+pr_err("RMPUPDATE failed for PFN %llx, pg_level: %d, ret: %d\n",+pfn,level,ret);dump_rmpentry(pfn);dump_stack();return-EFAULT;}+if(!state->assigned&&restore_direct_map(pfn,npages))+return-EFAULT;+return0;}
From: Michael Roth <hidden> Date: 2023-12-30 16:22:53
From: Brijesh Singh <redacted>
AMD introduced the next generation of SEV called SEV-SNP (Secure Nested
Paging). SEV-SNP builds upon existing SEV and SEV-ES functionality
while adding new hardware security protection.
Define the commands and structures used to communicate with the AMD-SP
when creating and managing the SEV-SNP guests. The SEV-SNP firmware spec
is available at developer.amd.com/sev.
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Brijesh Singh <redacted>
[mdr: update SNP command list and SNP status struct based on current
spec, use C99 flexible arrays, fix kernel-doc issues]
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 16 +++
include/linux/psp-sev.h | 264 +++++++++++++++++++++++++++++++++++
include/uapi/linux/psp-sev.h | 56 ++++++++
3 files changed, 336 insertions(+)
@@ -78,6 +78,36 @@ enum sev_cmd {SEV_CMD_DBG_DECRYPT=0x060,SEV_CMD_DBG_ENCRYPT=0x061,+/* SNP specific commands */+SEV_CMD_SNP_INIT=0x081,+SEV_CMD_SNP_SHUTDOWN=0x082,+SEV_CMD_SNP_PLATFORM_STATUS=0x083,+SEV_CMD_SNP_DF_FLUSH=0x084,+SEV_CMD_SNP_INIT_EX=0x085,+SEV_CMD_SNP_SHUTDOWN_EX=0x086,+SEV_CMD_SNP_DECOMMISSION=0x090,+SEV_CMD_SNP_ACTIVATE=0x091,+SEV_CMD_SNP_GUEST_STATUS=0x092,+SEV_CMD_SNP_GCTX_CREATE=0x093,+SEV_CMD_SNP_GUEST_REQUEST=0x094,+SEV_CMD_SNP_ACTIVATE_EX=0x095,+SEV_CMD_SNP_LAUNCH_START=0x0A0,+SEV_CMD_SNP_LAUNCH_UPDATE=0x0A1,+SEV_CMD_SNP_LAUNCH_FINISH=0x0A2,+SEV_CMD_SNP_DBG_DECRYPT=0x0B0,+SEV_CMD_SNP_DBG_ENCRYPT=0x0B1,+SEV_CMD_SNP_PAGE_SWAP_OUT=0x0C0,+SEV_CMD_SNP_PAGE_SWAP_IN=0x0C1,+SEV_CMD_SNP_PAGE_MOVE=0x0C2,+SEV_CMD_SNP_PAGE_MD_INIT=0x0C3,+SEV_CMD_SNP_PAGE_SET_STATE=0x0C6,+SEV_CMD_SNP_PAGE_RECLAIM=0x0C7,+SEV_CMD_SNP_PAGE_UNSMASH=0x0C8,+SEV_CMD_SNP_CONFIG=0x0C9,+SEV_CMD_SNP_DOWNLOAD_FIRMWARE_EX=0x0CA,+SEV_CMD_SNP_COMMIT=0x0CB,+SEV_CMD_SNP_VLEK_LOAD=0x0CD,+SEV_CMD_MAX,};
@@ -523,6 +553,240 @@ struct sev_data_attestation_report {u32len;/* In/Out */}__packed;+/**+*structsev_data_snp_download_firmware-SNP_DOWNLOAD_FIRMWAREcommandparams+*+*@address:physicaladdressoffirmwareimage+*@len:lenofthefirmwareimage+*/+structsev_data_snp_download_firmware{+u64address;/* In */+u32len;/* In */+}__packed;++/**+*structsev_data_snp_activate-SNP_ACTIVATEcommandparams+*+*@gctx_paddr:systemphysicaladdressguestcontextpage+*@asid:ASIDtobindtotheguest+*/+structsev_data_snp_activate{+u64gctx_paddr;/* In */+u32asid;/* In */+}__packed;++/**+*structsev_data_snp_addr-genericSNPcommandparams+*+*@gctx_paddr:systemphysicaladdressguestcontextpage+*/+structsev_data_snp_addr{+u64gctx_paddr;/* In */+}__packed;++/**+*structsev_data_snp_launch_start-SNP_LAUNCH_STARTcommandparams+*+*@gctx_paddr:systemphysicaladdressofguestcontextpage+*@policy:guestpolicy+*@ma_gctx_paddr:systemphysicaladdressofmigrationagent+*@ma_en:theguestisassociatedwithamigrationagent+*@imi_en:launchflowislaunchinganIncomingMigrationImageforthe+*purposeofguest-assistedmigration.+*@rsvd:reserved+*@gosvw:guestOS-visibleworkarounds,asdefinedbyhypervisor+*/+structsev_data_snp_launch_start{+u64gctx_paddr;/* In */+u64policy;/* In */+u64ma_gctx_paddr;/* In */+u32ma_en:1;/* In */+u32imi_en:1;/* In */+u32rsvd:30;+u8gosvw[16];/* In */+}__packed;++/* SNP support page type */+enum{+SNP_PAGE_TYPE_NORMAL=0x1,+SNP_PAGE_TYPE_VMSA=0x2,+SNP_PAGE_TYPE_ZERO=0x3,+SNP_PAGE_TYPE_UNMEASURED=0x4,+SNP_PAGE_TYPE_SECRET=0x5,+SNP_PAGE_TYPE_CPUID=0x6,++SNP_PAGE_TYPE_MAX+};++/**+*structsev_data_snp_launch_update-SNP_LAUNCH_UPDATEcommandparams+*+*@gctx_paddr:systemphysicaladdressofguestcontextpage+*@page_size:pagesize0indicates4Kand1indicates2MBpage+*@page_type:encodedpagetype+*@imi_page:indicatesthatthispageispartoftheIMIoftheguest+*@rsvd:reserved+*@rsvd2:reserved+*@address:systemphysicaladdressofdestinationpagetoencrypt+*@rsvd3:reserved+*@vmpl1_perms:VMPLpermissionmaskforVMPL1+*@vmpl2_perms:VMPLpermissionmaskforVMPL2+*@vmpl3_perms:VMPLpermissionmaskforVMPL3+*@rsvd4:reserved+*/+structsev_data_snp_launch_update{+u64gctx_paddr;/* In */+u32page_size:1;/* In */+u32page_type:3;/* In */+u32imi_page:1;/* In */+u32rsvd:27;+u32rsvd2;+u64address;/* In */+u32rsvd3:8;+u32vmpl1_perms:8;/* In */+u32vmpl2_perms:8;/* In */+u32vmpl3_perms:8;/* In */+u32rsvd4;+}__packed;++/**+*structsev_data_snp_launch_finish-SNP_LAUNCH_FINISHcommandparams+*+*@gctx_paddr:systemphysicaladdressofguestcontextpage+*@id_block_paddr:systemphysicaladdressofIDblock+*@id_auth_paddr:systemphysicaladdressofIDblockauthenticationstructure+*@id_block_en:indicateswhetherIDblockispresent+*@auth_key_en:indicateswhetherauthorkeyispresentinauthenticationstructure+*@rsvd:reserved+*@host_data:host-supplieddataforguest,notinterpretedbyfirmware+*/+structsev_data_snp_launch_finish{+u64gctx_paddr;+u64id_block_paddr;+u64id_auth_paddr;+u8id_block_en:1;+u8auth_key_en:1;+u64rsvd:62;+u8host_data[32];+}__packed;++/**+*structsev_data_snp_guest_status-SNP_GUEST_STATUScommandparams+*+*@gctx_paddr:systemphysicaladdressofguestcontextpage+*@address:systemphysicaladdressofgueststatuspage+*/+structsev_data_snp_guest_status{+u64gctx_paddr;+u64address;+}__packed;++/**+*structsev_data_snp_page_reclaim-SNP_PAGE_RECLAIMcommandparams+*+*@paddr:systemphysicaladdressofpagetobeclaimed.The0thbit+*intheaddressindicatesthepagesize.0hindicates4kBand+*1hindicates2MBpage.+*/+structsev_data_snp_page_reclaim{+u64paddr;+}__packed;++/**+*structsev_data_snp_page_unsmash-SNP_PAGE_UNSMASHcommandparams+*+*@paddr:systemphysicaladdressofpagetobeunsmashed.The0thbit+*intheaddressindicatesthepagesize.0hindicates4kBand+*1hindicates2MBpage.+*/+structsev_data_snp_page_unsmash{+u64paddr;+}__packed;++/**+*structsev_data_snp_dbg-DBG_ENCRYPT/DBG_DECRYPTcommandparameters+*+*@gctx_paddr:systemphysicaladdressofguestcontextpage+*@src_addr:sourceaddressofdatatooperateon+*@dst_addr:destinationaddressofdatatooperateon+*/+structsev_data_snp_dbg{+u64gctx_paddr;/* In */+u64src_addr;/* In */+u64dst_addr;/* In */+}__packed;++/**+*structsev_data_snp_guest_request-SNP_GUEST_REQUESTcommandparams+*+*@gctx_paddr:systemphysicaladdressofguestcontextpage+*@req_paddr:systemphysicaladdressofrequestpage+*@res_paddr:systemphysicaladdressofresponsepage+*/+structsev_data_snp_guest_request{+u64gctx_paddr;/* In */+u64req_paddr;/* In */+u64res_paddr;/* In */+}__packed;++/**+*structsev_data_snp_init_ex-SNP_INIT_EXstructure+*+*@init_rmp:indicatethattheRMPshouldbeinitialized.+*@list_paddr_en:indicatethatlist_paddrisvalid+*@rsvd:reserved+*@rsvd1:reserved+*@list_paddr:systemphysicaladdressofrangelist+*@rsvd2:reserved+*/+structsev_data_snp_init_ex{+u32init_rmp:1;+u32list_paddr_en:1;+u32rsvd:30;+u32rsvd1;+u64list_paddr;+u8rsvd2[48];+}__packed;++/**+*structsev_data_range-RANGEstructure+*+*@base:systemphysicaladdressoffirstbyteofrange+*@page_count:numberof4KBpagesinthisrange+*@rsvd:reserved+*/+structsev_data_range{+u64base;+u32page_count;+u32rsvd;+}__packed;++/**+*structsev_data_range_list-RANGE_LISTstructure+*+*@num_elements:numberofelementsinRANGE_ARRAY+*@rsvd:reserved+*@ranges:arrayofnum_elementsoftypeRANGE+*/+structsev_data_range_list{+u32num_elements;+u32rsvd;+structsev_data_rangeranges[];+}__packed;++/**+*structsev_data_snp_shutdown_ex-SNP_SHUTDOWN_EXstructure+*+*@length:lenofthecommandbufferreadbythePSP+*@iommu_snp_shutdown:DisableenforcementofSNPintheIOMMU+*@rsvd1:reserved+*/+structsev_data_snp_shutdown_ex{+u32length;+u32iommu_snp_shutdown:1;+u32rsvd1:31;+}__packed;+#ifdef CONFIG_CRYPTO_DEV_SP_PSP/**
@@ -155,6 +161,56 @@ struct sev_user_data_get_id2 {__u32length;/* In/Out */}__packed;+/**+*structsev_user_data_snp_status-SNPstatus+*+*@api_major:APImajorversion+*@api_minor:APIminorversion+*@state:currentplatformstate+*@is_rmp_initialized:whetherRMPisinitializedornot+*@rsvd:reserved+*@build_id:firmwarebuildidfortheAPIversion+*@mask_chip_id:whetherchipidispresentinattestationreportsornot+*@mask_chip_key:whetherattestationreportsaresignedornot+*@vlek_en:VLEKhashstickisloaded+*@rsvd1:reserved+*@guest_count:thenumberofguestcurrentlymanagedbythefirmware+*@current_tcb_version:currentTCBversion+*@reported_tcb_version:reportedTCBversion+*/+structsev_user_data_snp_status{+__u8api_major;/* Out */+__u8api_minor;/* Out */+__u8state;/* Out */+__u8is_rmp_initialized:1;/* Out */+__u8rsvd:7;+__u32build_id;/* Out */+__u32mask_chip_id:1;/* Out */+__u32mask_chip_key:1;/* Out */+__u32vlek_en:1;/* Out */+__u32rsvd1:29;+__u32guest_count;/* Out */+__u64current_tcb_version;/* Out */+__u64reported_tcb_version;/* Out */+}__packed;++/**+*structsev_user_data_snp_config-systemwideconfigurationvalueforSNP.+*+*@reported_tcb:theTCBversiontoreportintheguestattestationreport.+*@mask_chip_id:whetherchipidispresentinattestationreportsornot+*@mask_chip_key:whetherattestationreportsaresignedornot+*@rsvd:reserved+*@rsvd1:reserved+*/+structsev_user_data_snp_config{+__u64reported_tcb;/* In */+__u32mask_chip_id:1;/* In */+__u32mask_chip_key:1;/* In */+__u32rsvd:30;/* In */+__u8rsvd1[52];+}__packed;+/***structsev_issue_cmd-SEVioctlparameters*
From: Michael Roth <hidden> Date: 2023-12-30 16:23:16
From: Brijesh Singh <redacted>
Before SNP VMs can be launched, the platform must be appropriately
configured and initialized. Platform initialization is accomplished via
the SNP_INIT command. Make sure to do a WBINVD and issue DF_FLUSH
command to prepare for the first SNP guest launch after INIT.
During the execution of SNP_INIT command, the firmware configures
and enables SNP security policy enforcement in many system components.
Some system components write to regions of memory reserved by early
x86 firmware (e.g. UEFI). Other system components write to regions
provided by the operation system, hypervisor, or x86 firmware.
Such system components can only write to HV-fixed pages or Default
pages. They will error when attempting to write to other page states
after SNP_INIT enables their SNP enforcement.
Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list of
system physical address ranges to convert into the HV-fixed page states
during the RMP initialization. If INIT_RMP is 1, hypervisors should
provide all system physical address ranges that the hypervisor will
never assign to a guest until the next RMP re-initialization.
For instance, the memory that UEFI reserves should be included in the
range list. This allows system components that occasionally write to
memory (e.g. logging to UEFI reserved regions) to not fail due to
RMP initialization and SNP enablement.
Note that SNP_INIT(_EX) must not be executed while non-SEV guests are
executing, otherwise it is possible that the system could reset or hang.
The psp_init_on_probe module parameter was added for SEV/SEV-ES support
and the init_ex_path module parameter to allow for time for the
necessary file system to be mounted/available. SNP_INIT(_EX) does not
use the file associated with init_ex_path. So, to avoid running into
issues where SNP_INIT(_EX) is called while there are other running
guests, issue it during module probe regardless of the psp_init_on_probe
setting, but maintain the previous deferrable handling for SEV/SEV-ES
initialization.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Co-developed-by: Jarkko Sakkinen <redacted>
Signed-off-by: Jarkko Sakkinen <redacted>
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
[mdr: squash in psp_init_on_probe changes from Tom, reduce
proliferation of 'probe' function parameter where possible]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/kvm/svm/sev.c | 5 +-
drivers/crypto/ccp/sev-dev.c | 282 ++++++++++++++++++++++++++++++++---
drivers/crypto/ccp/sev-dev.h | 2 +
include/linux/psp-sev.h | 17 ++-
4 files changed, 283 insertions(+), 23 deletions(-)
@@ -480,20 +492,165 @@ static inline int __sev_do_init_locked(int *psp_ret)return__sev_init_locked(psp_ret);}-staticint__sev_platform_init_locked(int*error)+staticvoidsnp_set_hsave_pa(void*arg)+{+wrmsrl(MSR_VM_HSAVE_PA,0);+}++staticintsnp_filter_reserved_mem_regions(structresource*rs,void*arg)+{+structsev_data_range_list*range_list=arg;+structsev_data_range*range=&range_list->ranges[range_list->num_elements];+size_tsize;++/*+*EnsurethelistofHV_FIXEDpagesthatwillbepassedtofirmware+*donotexceedthepage-sizedargumentbuffer.+*/+if((range_list->num_elements*sizeof(structsev_data_range)++sizeof(structsev_data_range_list))>PAGE_SIZE)+return-E2BIG;++switch(rs->desc){+caseE820_TYPE_RESERVED:+caseE820_TYPE_PMEM:+caseE820_TYPE_ACPI:+range->base=rs->start&PAGE_MASK;+size=(rs->end+1)-rs->start;+range->page_count=size>>PAGE_SHIFT;+range_list->num_elements++;+break;+default:+break;+}++return0;+}++staticint__sev_snp_init_locked(int*error){-intrc=0,psp_ret=SEV_RET_NO_FW_CALL;structpsp_device*psp=psp_master;+structsev_data_snp_init_exdata;structsev_device*sev;+void*arg=&data;+intcmd,rc=0;-if(!psp||!psp->sev_data)+if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))return-ENODEV;sev=psp->sev_data;+if(sev->snp_initialized)+return0;++if(!sev_version_greater_or_equal(SNP_MIN_API_MAJOR,SNP_MIN_API_MINOR)){+dev_dbg(sev->dev,"SEV-SNP support requires firmware version >= %d:%d\n",+SNP_MIN_API_MAJOR,SNP_MIN_API_MINOR);+return0;+}++/*+*TheSNP_INITrequirestheMSR_VM_HSAVE_PAmustbesetto0h+*acrossallcores.+*/+on_each_cpu(snp_set_hsave_pa,NULL,1);++/*+*StartinginSNPfirmwarev1.52,theSNP_INIT_EXcommandtakesalistof+*systemphysicaladdressrangestoconvertintotheHV-fixedpagestates+*duringtheRMPinitialization.Forinstance,thememorythatUEFI+*reservesshouldbeincludedintherangelist.Thisallowssystem+*componentsthatoccasionallywritetomemory(e.g.loggingtoUEFI+*reservedregions)tonotfailduetoRMPinitializationandSNPenablement.+*/+if(sev_version_greater_or_equal(SNP_MIN_API_MAJOR,52)){+/*+*Firmwarechecksthatthepagescontainingtherangesenumerated+*intheRANGESstructureareeitherintheDefaultpagestateorinthe+*firmwarepagestate.+*/+snp_range_list=kzalloc(PAGE_SIZE,GFP_KERNEL);+if(!snp_range_list){+dev_err(sev->dev,+"SEV: SNP_INIT_EX range list memory allocation failed\n");+return-ENOMEM;+}++/*+*RetrieveallreservedmemoryregionssetupbyUEFIfromthee820memorymap+*tobesetupasHV-fixedpages.+*/+rc=walk_iomem_res_desc(IORES_DESC_NONE,IORESOURCE_MEM,0,~0,+snp_range_list,snp_filter_reserved_mem_regions);+if(rc){+dev_err(sev->dev,+"SEV: SNP_INIT_EX walk_iomem_res_desc failed rc = %d\n",rc);+returnrc;+}++memset(&data,0,sizeof(data));+data.init_rmp=1;+data.list_paddr_en=1;+data.list_paddr=__psp_pa(snp_range_list);+cmd=SEV_CMD_SNP_INIT_EX;+}else{+cmd=SEV_CMD_SNP_INIT;+arg=NULL;+}++/*+*Thefollowingsequencemustbeissuedbeforelaunchingthe+*firstSNPguesttoensurealldirtycachelinesareflushed,+*includingfromupdatestotheRMPtableitselfviaRMPUPDATE+*instructions:+*+*-WBINDVonallrunningCPUs+*-SEV_CMD_SNP_INIT[_EX]firmwarecommand+*-WBINDVonallrunningCPUs+*-SEV_CMD_SNP_DF_FLUSHfirmwarecommand+*/+wbinvd_on_all_cpus();++rc=__sev_do_cmd_locked(cmd,arg,error);+if(rc)+returnrc;++/* Prepare for first SNP guest launch after INIT. */+wbinvd_on_all_cpus();+rc=__sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH,NULL,error);+if(rc)+returnrc;++sev->snp_initialized=true;+dev_dbg(sev->dev,"SEV-SNP firmware initialized\n");++returnrc;+}++staticint__sev_platform_init_locked(int*error)+{+intrc,psp_ret=SEV_RET_NO_FW_CALL;+structsev_device*sev;++if(!psp_master||!psp_master->sev_data)+return-ENODEV;++sev=psp_master->sev_data;+if(sev->state==SEV_STATE_INIT)return0;+if(!sev_es_tmr){+/* Obtain the TMR memory area for SEV-ES use */+sev_es_tmr=sev_fw_alloc(SEV_ES_TMR_SIZE);+if(sev_es_tmr)+/* Must flush the cache before giving it to the firmware */+clflush_cache_range(sev_es_tmr,SEV_ES_TMR_SIZE);+else+dev_warn(sev->dev,+"SEV: TMR allocation failed, SEV-ES support unavailable\n");+}+if(sev_init_ex_buffer){rc=sev_read_init_ex_file();if(rc)
@@ -536,12 +693,46 @@ static int __sev_platform_init_locked(int *error)return0;}-intsev_platform_init(int*error)+staticint_sev_platform_init_locked(structsev_platform_init_args*args)+{+structsev_device*sev;+intrc;++if(!psp_master||!psp_master->sev_data)+return-ENODEV;++sev=psp_master->sev_data;++if(sev->state==SEV_STATE_INIT)+return0;++/*+*LegacyguestscannotberunningwhileSNP_INIT(_EX)isexecuting,+*soperformSEV-SNPinitializationatprobetime.+*/+rc=__sev_snp_init_locked(&args->error);+if(rc&&rc!=-ENODEV){+/*+*Don'taborttheprobeifSNPINITfailed,+*continuetoinitializethelegacySEVfirmware.+*/+dev_err(sev->dev,"SEV-SNP: failed to INIT rc %d, error %#x\n",+rc,args->error);+}++/* Defer legacy SEV/SEV-ES support if allowed by caller/module. */+if(args->probe&&!psp_init_on_probe)+return0;++return__sev_platform_init_locked(&args->error);+}++intsev_platform_init(structsev_platform_init_args*args){intrc;mutex_lock(&sev_cmd_mutex);-rc=__sev_platform_init_locked(error);+rc=_sev_platform_init_locked(args);mutex_unlock(&sev_cmd_mutex);returnrc;
@@ -852,6 +1043,55 @@ static int sev_update_firmware(struct device *dev)returnret;}+staticint__sev_snp_shutdown_locked(int*error)+{+structsev_device*sev=psp_master->sev_data;+structsev_data_snp_shutdown_exdata;+intret;++if(!sev->snp_initialized)+return0;++memset(&data,0,sizeof(data));+data.length=sizeof(data);+data.iommu_snp_shutdown=1;++wbinvd_on_all_cpus();++ret=__sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN_EX,&data,error);+/* SHUTDOWN may require DF_FLUSH */+if(*error==SEV_RET_DFFLUSH_REQUIRED){+ret=__sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH,NULL,NULL);+if(ret){+dev_err(sev->dev,"SEV-SNP DF_FLUSH failed\n");+returnret;+}+/* reissue the shutdown command */+ret=__sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN_EX,&data,+error);+}+if(ret){+dev_err(sev->dev,"SEV-SNP firmware shutdown failed\n");+returnret;+}++sev->snp_initialized=false;+dev_dbg(sev->dev,"SEV-SNP firmware shutdown\n");++returnret;+}++staticintsev_snp_shutdown(int*error)+{+intrc;++mutex_lock(&sev_cmd_mutex);+rc=__sev_snp_shutdown_locked(error);+mutex_unlock(&sev_cmd_mutex);++returnrc;+}+staticintsev_ioctl_do_pek_import(structsev_issue_cmd*argp,boolwritable){structsev_device*sev=psp_master->sev_data;
@@ -1299,6 +1539,8 @@ int sev_dev_init(struct psp_device *psp)staticvoidsev_firmware_shutdown(structsev_device*sev){+interror;+sev_platform_shutdown(NULL);if(sev_es_tmr){
@@ -1370,23 +1620,15 @@ void sev_pci_init(void)}}-/* Obtain the TMR memory area for SEV-ES use */-sev_es_tmr=sev_fw_alloc(SEV_ES_TMR_SIZE);-if(sev_es_tmr)-/* Must flush the cache before giving it to the firmware */-clflush_cache_range(sev_es_tmr,SEV_ES_TMR_SIZE);-else-dev_warn(sev->dev,-"SEV: TMR allocation failed, SEV-ES support unavailable\n");--if(!psp_init_on_probe)-return;-/* Initialize the platform */-rc=sev_platform_init(&error);+args.probe=true;+rc=sev_platform_init(&args);if(rc)dev_err(sev->dev,"SEV: failed to INIT error %#x, rc %d\n",-error,rc);+args.error,rc);++dev_info(sev->dev,"SEV%s API:%d.%d build:%d\n",sev->snp_initialized?+"-SNP":"",sev->api_major,sev->api_minor,sev->build);return;
From: Michael Roth <hidden> Date: 2023-12-30 16:23:34
From: Brijesh Singh <redacted>
Export sev_do_cmd() as a generic API for the hypervisor to issue
commands to manage an SEV and SNP guest. The commands for SEV and SNP
are defined in the SEV and SEV-SNP firmware specifications.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: kernel-doc fixups]
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 3 ++-
include/linux/psp-sev.h | 19 +++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
From: Michael Roth <hidden> Date: 2023-12-30 16:23:54
From: Ashish Kalra <ashish.kalra@amd.com>
Pages are unsafe to be released back to the page-allocator, if they
have been transitioned to firmware/guest state and can't be reclaimed
or transitioned back to hypervisor/shared state. In this case add
them to an internal leaked pages list to ensure that they are not freed
or touched/accessed to cause fatal page faults.
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: relocate to arch/x86/virt/svm/sev.c]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/sev.h | 2 ++
arch/x86/virt/svm/sev.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
@@ -60,6 +60,17 @@ static u64 probed_rmp_base, probed_rmp_size;staticstructrmpentry*rmptable__ro_after_init;staticu64rmptable_max_pfn__ro_after_init;+/* List of pages which are leaked and cannot be reclaimed */+structleaked_page{+structpage*page;+structlist_headlist;+};++staticLIST_HEAD(snp_leaked_pages_list);+staticDEFINE_SPINLOCK(snp_leaked_pages_list_lock);++staticunsignedlongsnp_nr_leaked_pages;+#undef pr_fmt#define pr_fmt(fmt) "SEV-SNP: " fmt
@@ -476,3 +487,27 @@ int rmp_make_shared(u64 pfn, enum pg_level level)returnrmpupdate(pfn,&state);}EXPORT_SYMBOL_GPL(rmp_make_shared);++voidsnp_leak_pages(u64pfn,unsignedintnpages)+{+structpage*page=pfn_to_page(pfn);+structleaked_page*leak;++pr_debug("%s: leaking PFN range 0x%llx-0x%llx\n",__func__,pfn,pfn+npages);++spin_lock(&snp_leaked_pages_list_lock);+while(npages--){+leak=kzalloc(sizeof(*leak),GFP_KERNEL_ACCOUNT);+if(!leak)+gotounlock;+leak->page=page;+list_add_tail(&leak->list,&snp_leaked_pages_list);+dump_rmpentry(pfn);+snp_nr_leaked_pages++;+pfn++;+page++;+}+unlock:+spin_unlock(&snp_leaked_pages_list_lock);+}+EXPORT_SYMBOL_GPL(snp_leak_pages);
From: Michael Roth <hidden> Date: 2023-12-30 16:24:19
From: Brijesh Singh <redacted>
The behavior and requirement for the SEV-legacy command is altered when
the SNP firmware is in the INIT state. See SEV-SNP firmware specification
for more details.
Allocate the Trusted Memory Region (TMR) as a 2mb sized/aligned region
when SNP is enabled to satisfy new requirements for SNP. Continue
allocating a 1mb region for !SNP configuration.
While at it, provide an API to allocate a firmware page. The KVM driver
needs to allocate a firmware context page during the guest creation. The
context page needs to be updated by the firmware. See the SEV-SNP
specification for further details.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: use struct sev_data_snp_page_reclaim instead of passing paddr
directly to SEV_CMD_SNP_PAGE_RECLAIM]
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 176 +++++++++++++++++++++++++++++++----
include/linux/psp-sev.h | 9 ++
2 files changed, 165 insertions(+), 20 deletions(-)
@@ -192,17 +198,6 @@ static int sev_cmd_buffer_len(int cmd)return0;}-staticvoid*sev_fw_alloc(unsignedlonglen)-{-structpage*page;--page=alloc_pages(GFP_KERNEL,get_order(len));-if(!page)-returnNULL;--returnpage_address(page);-}-staticstructfile*open_file_as_root(constchar*filename,intflags,umode_tmode){structfile*fp;
@@ -333,6 +328,142 @@ static int sev_write_init_ex_file_if_required(int cmd_id)returnsev_write_init_ex_file();}+/*+*snp_reclaim_pages()needs__sev_do_cmd_locked(),and__sev_do_cmd_locked()+*needssnp_reclaim_pages(),soaforwarddeclarationisneeded.+*/+staticint__sev_do_cmd_locked(intcmd,void*data,int*psp_ret);++staticintsnp_reclaim_pages(unsignedlongpaddr,unsignedintnpages,boollocked)+{+intret,err,i;++paddr=__sme_clr(paddr);++for(i=0;i<npages;i++,paddr+=PAGE_SIZE){+structsev_data_snp_page_reclaimdata={0};++data.paddr=paddr;++if(locked)+ret=__sev_do_cmd_locked(SEV_CMD_SNP_PAGE_RECLAIM,&data,&err);+else+ret=sev_do_cmd(SEV_CMD_SNP_PAGE_RECLAIM,&data,&err);++if(ret)+gotocleanup;++ret=rmp_make_shared(__phys_to_pfn(paddr),PG_LEVEL_4K);+if(ret)+gotocleanup;+}++return0;++cleanup:+/*+*Iftherewasafailurereclaimingthepagethenitisnolongersafe+*toreleaseitbacktothesystem;leakitinstead.+*/+snp_leak_pages(__phys_to_pfn(paddr),npages-i);+returnret;+}++staticintrmp_mark_pages_firmware(unsignedlongpaddr,unsignedintnpages,boollocked)+{+unsignedlongpfn=__sme_clr(paddr)>>PAGE_SHIFT;+intrc,i;++for(i=0;i<npages;i++,pfn++){+rc=rmp_make_private(pfn,0,PG_LEVEL_4K,0,true);+if(rc)+gotocleanup;+}++return0;++cleanup:+/*+*Tryunrollingthefirmwarestatechangesby+*reclaimingthepageswhichwerealreadychangedtothe+*firmwarestate.+*/+snp_reclaim_pages(paddr,i,locked);++returnrc;+}++staticstructpage*__snp_alloc_firmware_pages(gfp_tgfp_mask,intorder)+{+unsignedlongnpages=1ul<<order,paddr;+structsev_device*sev;+structpage*page;++if(!psp_master||!psp_master->sev_data)+returnNULL;++page=alloc_pages(gfp_mask,order);+if(!page)+returnNULL;++/* If SEV-SNP is initialized then add the page in RMP table. */+sev=psp_master->sev_data;+if(!sev->snp_initialized)+returnpage;++paddr=__pa((unsignedlong)page_address(page));+if(rmp_mark_pages_firmware(paddr,npages,false))+returnNULL;++returnpage;+}++void*snp_alloc_firmware_page(gfp_tgfp_mask)+{+structpage*page;++page=__snp_alloc_firmware_pages(gfp_mask,0);++returnpage?page_address(page):NULL;+}+EXPORT_SYMBOL_GPL(snp_alloc_firmware_page);++staticvoid__snp_free_firmware_pages(structpage*page,intorder,boollocked)+{+structsev_device*sev=psp_master->sev_data;+unsignedlongpaddr,npages=1ul<<order;++if(!page)+return;++paddr=__pa((unsignedlong)page_address(page));+if(sev->snp_initialized&&+snp_reclaim_pages(paddr,npages,locked))+return;++__free_pages(page,order);+}++voidsnp_free_firmware_page(void*addr)+{+if(!addr)+return;++__snp_free_firmware_pages(virt_to_page(addr),0,false);+}+EXPORT_SYMBOL_GPL(snp_free_firmware_page);++staticvoid*sev_fw_alloc(unsignedlonglen)+{+structpage*page;++page=__snp_alloc_firmware_pages(GFP_KERNEL,get_order(len));+if(!page)+returnNULL;++returnpage_address(page);+}+staticint__sev_do_cmd_locked(intcmd,void*data,int*psp_ret){structpsp_device*psp=psp_master;
@@ -456,7 +587,7 @@ static int __sev_init_locked(int *error)data.tmr_address=__pa(sev_es_tmr);data.flags|=SEV_INIT_FLAGS_SEV_ES;-data.tmr_len=SEV_ES_TMR_SIZE;+data.tmr_len=sev_es_tmr_size;}return__sev_do_cmd_locked(SEV_CMD_INIT,&data,error);
@@ -479,7 +610,7 @@ static int __sev_init_ex_locked(int *error)data.tmr_address=__pa(sev_es_tmr);data.flags|=SEV_INIT_FLAGS_SEV_ES;-data.tmr_len=SEV_ES_TMR_SIZE;+data.tmr_len=sev_es_tmr_size;}return__sev_do_cmd_locked(SEV_CMD_INIT_EX,&data,error);
@@ -625,6 +756,8 @@ static int __sev_snp_init_locked(int *error)sev->snp_initialized=true;dev_dbg(sev->dev,"SEV-SNP firmware initialized\n");+sev_es_tmr_size=SNP_TMR_SIZE;+returnrc;}
@@ -643,14 +776,16 @@ static int __sev_platform_init_locked(int *error)if(!sev_es_tmr){/* Obtain the TMR memory area for SEV-ES use */-sev_es_tmr=sev_fw_alloc(SEV_ES_TMR_SIZE);-if(sev_es_tmr)+sev_es_tmr=sev_fw_alloc(sev_es_tmr_size);+if(sev_es_tmr){/* Must flush the cache before giving it to the firmware */-clflush_cache_range(sev_es_tmr,SEV_ES_TMR_SIZE);-else+if(!sev->snp_initialized)+clflush_cache_range(sev_es_tmr,sev_es_tmr_size);+}else{dev_warn(sev->dev,"SEV: TMR allocation failed, SEV-ES support unavailable\n");}+}if(sev_init_ex_buffer){rc=sev_read_init_ex_file();
@@ -1548,8 +1683,9 @@ static void sev_firmware_shutdown(struct sev_device *sev)/* The TMR area was encrypted, flush it from the cache */wbinvd_on_all_cpus();-free_pages((unsignedlong)sev_es_tmr,-get_order(SEV_ES_TMR_SIZE));+__snp_free_firmware_pages(virt_to_page(sev_es_tmr),+get_order(sev_es_tmr_size),+false);sev_es_tmr=NULL;}
From: Michael Roth <hidden> Date: 2023-12-30 16:24:36
From: Tom Lendacky <thomas.lendacky@amd.com>
For SEV/SEV-ES, a buffer can be used to access non-volatile data so it
can be initialized from a specified by the init_ex_path CCP module
parameter instead of relying on the SPI bus for NV storage, and
afterward the buffer can be read from to sync new data back to the file.
When SNP is enabled, the pages comprising this buffer need to be set to
firmware-owned in the RMP table before they can be accessed by firmware
for subsequent updates to the initial contents.
Implement that handling here.
Setting these pages to firmware-owned will also result in them being
removed from the kernel direct map, since generally the hypervisor does
not access firmware-owned pages. However, in this exceptional case,
the hypervisor does need to read the buffer to transfer updated contents
back to the file at init_ex_path.
Support this by using vmap() to create temporary mappings to the
firmware-owned buffer whenever accesses are made, and rework the
existing code to track the struct page corresponding to the start of the
buffer rather than the virtual address that was previously provided via
the direct map.
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Co-developed-by: Michael Roth <redacted>
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 104 ++++++++++++++++++++++++++---------
1 file changed, 79 insertions(+), 25 deletions(-)
@@ -231,7 +232,7 @@ static int sev_read_init_ex_file(void)lockdep_assert_held(&sev_cmd_mutex);-if(!sev_init_ex_buffer)+if(!sev_init_ex_page)return-EOPNOTSUPP;fp=open_file_as_root(init_ex_path,O_RDONLY,0);
@@ -251,7 +252,7 @@ static int sev_read_init_ex_file(void)returnret;}-nread=kernel_read(fp,sev_init_ex_buffer,NV_LENGTH,NULL);+nread=kernel_read(fp,page_to_virt(sev_init_ex_page),NV_LENGTH,NULL);if(nread!=NV_LENGTH){dev_info(sev->dev,"SEV: could not read %u bytes to non volatile memory area, ret %ld\n",
@@ -264,16 +265,44 @@ static int sev_read_init_ex_file(void)return0;}+/*+*WhenSNPisenabled,thepagescomprisingthebufferusedtopopulate+*thefilespecifiedbytheinit_ex_pathmoduleparameterneedstobeset+*tofirmware-owned,whichremovesthemappingfromthekerneldirect+*mappingsincegenerallythehypervisordoesnotaccessfirmware-owned+*pages.However,inthiscasethehypervisordoesneedtoreadthe+*buffertotransferthecontentstothefileatinit_ex_path,sothis+*functionisusedtocreateatemporaryvirtualmappingtobeusedfor+*thispurpose.+*/+staticvoid*vmap_sev_init_ex_buffer(void)+{+structpage*pages[NV_PAGES];+unsignedlongbase_pfn;+inti;++if(WARN_ON_ONCE(!sev_init_ex_page))+returnNULL;++base_pfn=page_to_pfn(sev_init_ex_page);++for(i=0;i<NV_PAGES;i++)+pages[i]=pfn_to_page(base_pfn+i);++returnvmap(pages,NV_PAGES,VM_MAP,PAGE_KERNEL_RO);+}+staticintsev_write_init_ex_file(void){structsev_device*sev=psp_master->sev_data;+void*sev_init_ex_buffer;structfile*fp;loff_toffset=0;ssize_tnwrite;lockdep_assert_held(&sev_cmd_mutex);-if(!sev_init_ex_buffer)+if(!sev_init_ex_page)return0;fp=open_file_as_root(init_ex_path,O_CREAT|O_WRONLY,0600);
@@ -286,6 +315,12 @@ static int sev_write_init_ex_file(void)returnret;}+sev_init_ex_buffer=vmap_sev_init_ex_buffer();+if(!sev_init_ex_buffer){+dev_err(sev->dev,"SEV: failed to map non-volative memory area\n");+return-EIO;+}+nwrite=kernel_write(fp,sev_init_ex_buffer,NV_LENGTH,&offset);vfs_fsync(fp,0);filp_close(fp,NULL);
@@ -294,10 +329,12 @@ static int sev_write_init_ex_file(void)dev_err(sev->dev,"SEV: failed to write %u bytes to non volatile memory area, ret %ld\n",NV_LENGTH,nwrite);+vunmap(sev_init_ex_buffer);return-EIO;}dev_dbg(sev->dev,"SEV: write successful to NV file\n");+vunmap(sev_init_ex_buffer);return0;}
@@ -306,7 +343,7 @@ static int sev_write_init_ex_file_if_required(int cmd_id){lockdep_assert_held(&sev_cmd_mutex);-if(!sev_init_ex_buffer)+if(!sev_init_ex_page)return0;/*
@@ -599,7 +636,7 @@ static int __sev_init_ex_locked(int *error)memset(&data,0,sizeof(data));data.length=sizeof(data);-data.nv_address=__psp_pa(sev_init_ex_buffer);+data.nv_address=sme_me_mask|PFN_PHYS(page_to_pfn(sev_init_ex_page));data.nv_len=NV_LENGTH;if(sev_es_tmr){
@@ -618,7 +655,7 @@ static int __sev_init_ex_locked(int *error)staticinlineint__sev_do_init_locked(int*psp_ret){-if(sev_init_ex_buffer)+if(sev_init_ex_page)return__sev_init_ex_locked(psp_ret);elsereturn__sev_init_locked(psp_ret);
@@ -787,10 +824,38 @@ static int __sev_platform_init_locked(int *error)}}-if(sev_init_ex_buffer){+/*+*Ifaninit_ex_pathisprovidedallocateabufferforthefileand+*readinthecontents.Additionally,ifSNPisinitialized,convert+*thebufferpagestofirmwarepages.+*/+if(init_ex_path&&!sev_init_ex_page){+structpage*page;++page=alloc_pages(GFP_KERNEL,get_order(NV_LENGTH));+if(!page){+dev_err(sev->dev,"SEV: INIT_EX NV memory allocation failed\n");+return-ENOMEM;+}++sev_init_ex_page=page;+rc=sev_read_init_ex_file();if(rc)returnrc;++/* If SEV-SNP is initialized, transition to firmware page. */+if(sev->snp_initialized){+unsignedlongnpages;++npages=1UL<<get_order(NV_LENGTH);+if(rmp_mark_pages_firmware(PFN_PHYS(page_to_pfn(sev_init_ex_page)),+npages,false)){+dev_err(sev->dev,+"SEV: INIT_EX NV memory page state change failed.\n");+return-ENOMEM;+}+}}rc=__sev_do_init_locked(&psp_ret);
@@ -1745,18 +1811,6 @@ void sev_pci_init(void)if(sev_update_firmware(sev->dev)==0)sev_get_api_version();-/* If an init_ex_path is provided rely on INIT_EX for PSP initialization-*insteadofINIT.-*/-if(init_ex_path){-sev_init_ex_buffer=sev_fw_alloc(NV_LENGTH);-if(!sev_init_ex_buffer){-dev_err(sev->dev,-"SEV: INIT_EX NV memory allocation failed\n");-gotoerr;-}-}-/* Initialize the platform */args.probe=true;rc=sev_platform_init(&args);
From: Michael Roth <hidden> Date: 2023-12-30 16:25:22
From: Brijesh Singh <redacted>
The behavior of legacy SEV commands is altered when the firmware is
initialized for SNP support. In that case, all command buffer memory
that may get written to by legacy SEV commands must be marked as
firmware-owned in the RMP table prior to issuing the command.
Additionally, when a command buffer contains a system physical address
that points to additional buffers that firmware may write to, special
handling is needed depending on whether:
1) the system physical address points to guest memory
2) the system physical address points to host memory
To handle case #1, the pages of these buffers are changed to
firmware-owned in the RMP table before issuing the command, and restored
to after the command completes.
For case #2, a bounce buffer is used instead of the original address.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Michael Roth <redacted>
Signed-off-by: Michael Roth <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
---
drivers/crypto/ccp/sev-dev.c | 421 ++++++++++++++++++++++++++++++++++-
drivers/crypto/ccp/sev-dev.h | 3 +
2 files changed, 414 insertions(+), 10 deletions(-)
@@ -43,6 +43,15 @@#define SNP_MIN_API_MAJOR 1#define SNP_MIN_API_MINOR 51+/*+*Maximumnumberoffirmware-writablebuffersthatmightbespecified+*intheparametersofalegacySEVcommandbuffer.+*/+#define CMD_BUF_FW_WRITABLE_MAX 2++/* Leave room in the descriptor array for an end-of-list indicator. */+#define CMD_BUF_DESC_MAX (CMD_BUF_FW_WRITABLE_MAX + 1)+staticDEFINE_MUTEX(sev_cmd_mutex);staticstructsev_misc_dev*misc_dev;
@@ -501,13 +510,351 @@ static void *sev_fw_alloc(unsigned long len)returnpage_address(page);}+/**+*structcmd_buf_desc-descriptorsformanaginglegacySEVcommandaddress+*parameterscorrespondingtobuffersthatmaybewrittentobyfirmware.+*+*@paddr_ptr:pointertheaddressparameterinthecommandbuffer,whichmay+*needtobesaved/restoreddependingonwhetherabouncebufferisused.+*MustbeNULLifthisdescriptorisonlyanend-of-listindicator.+*@paddr_orig:storagefortheoriginaladdressparameter,whichcanbeusedto+*restoretheoriginalvaluein@paddr_ptrincaseswhereitisreplaced+*withtheaddressofabouncebuffer.+*@len:lengthofbufferlocatedattheaddressoriginallystoredat@paddr_ptr+*@guest_owned:trueiftheaddresscorrespondstoguest-ownedpages,inwhich+*casebouncebuffersarenotneeded.+*/+structcmd_buf_desc{+u64*paddr_ptr;+u64paddr_orig;+u32len;+boolguest_owned;+};++/*+*IfalegacySEVcommandparameterisamemoryaddress,thosepagesin+*turnneedtobetransitionedto/fromfirmware-ownedbefore/after+*executingthefirmwarecommand.+*+*Additionally,incaseswherethosepagesarenotguest-owned,abounce+*bufferisneededinplaceoftheoriginalmemoryaddressparameter.+*+*Asetofdescriptorsareusedtokeeptrackofthishandling,and+*initializedherebasedonthespecificcommandsbeingexecuted.+*/+staticvoidsnp_populate_cmd_buf_desc_list(intcmd,void*cmd_buf,+structcmd_buf_desc*desc_list)+{+switch(cmd){+caseSEV_CMD_PDH_CERT_EXPORT:{+structsev_data_pdh_cert_export*data=cmd_buf;++desc_list[0].paddr_ptr=&data->pdh_cert_address;+desc_list[0].len=data->pdh_cert_len;+desc_list[1].paddr_ptr=&data->cert_chain_address;+desc_list[1].len=data->cert_chain_len;+break;+}+caseSEV_CMD_GET_ID:{+structsev_data_get_id*data=cmd_buf;++desc_list[0].paddr_ptr=&data->address;+desc_list[0].len=data->len;+break;+}+caseSEV_CMD_PEK_CSR:{+structsev_data_pek_csr*data=cmd_buf;++desc_list[0].paddr_ptr=&data->address;+desc_list[0].len=data->len;+break;+}+caseSEV_CMD_LAUNCH_UPDATE_DATA:{+structsev_data_launch_update_data*data=cmd_buf;++desc_list[0].paddr_ptr=&data->address;+desc_list[0].len=data->len;+desc_list[0].guest_owned=true;+break;+}+caseSEV_CMD_LAUNCH_UPDATE_VMSA:{+structsev_data_launch_update_vmsa*data=cmd_buf;++desc_list[0].paddr_ptr=&data->address;+desc_list[0].len=data->len;+desc_list[0].guest_owned=true;+break;+}+caseSEV_CMD_LAUNCH_MEASURE:{+structsev_data_launch_measure*data=cmd_buf;++desc_list[0].paddr_ptr=&data->address;+desc_list[0].len=data->len;+break;+}+caseSEV_CMD_LAUNCH_UPDATE_SECRET:{+structsev_data_launch_secret*data=cmd_buf;++desc_list[0].paddr_ptr=&data->guest_address;+desc_list[0].len=data->guest_len;+desc_list[0].guest_owned=true;+break;+}+caseSEV_CMD_DBG_DECRYPT:{+structsev_data_dbg*data=cmd_buf;++desc_list[0].paddr_ptr=&data->dst_addr;+desc_list[0].len=data->len;+desc_list[0].guest_owned=true;+break;+}+caseSEV_CMD_DBG_ENCRYPT:{+structsev_data_dbg*data=cmd_buf;++desc_list[0].paddr_ptr=&data->dst_addr;+desc_list[0].len=data->len;+desc_list[0].guest_owned=true;+break;+}+caseSEV_CMD_ATTESTATION_REPORT:{+structsev_data_attestation_report*data=cmd_buf;++desc_list[0].paddr_ptr=&data->address;+desc_list[0].len=data->len;+break;+}+caseSEV_CMD_SEND_START:{+structsev_data_send_start*data=cmd_buf;++desc_list[0].paddr_ptr=&data->session_address;+desc_list[0].len=data->session_len;+break;+}+caseSEV_CMD_SEND_UPDATE_DATA:{+structsev_data_send_update_data*data=cmd_buf;++desc_list[0].paddr_ptr=&data->hdr_address;+desc_list[0].len=data->hdr_len;+desc_list[1].paddr_ptr=&data->trans_address;+desc_list[1].len=data->trans_len;+break;+}+caseSEV_CMD_SEND_UPDATE_VMSA:{+structsev_data_send_update_vmsa*data=cmd_buf;++desc_list[0].paddr_ptr=&data->hdr_address;+desc_list[0].len=data->hdr_len;+desc_list[1].paddr_ptr=&data->trans_address;+desc_list[1].len=data->trans_len;+break;+}+caseSEV_CMD_RECEIVE_UPDATE_DATA:{+structsev_data_receive_update_data*data=cmd_buf;++desc_list[0].paddr_ptr=&data->guest_address;+desc_list[0].len=data->guest_len;+desc_list[0].guest_owned=true;+break;+}+caseSEV_CMD_RECEIVE_UPDATE_VMSA:{+structsev_data_receive_update_vmsa*data=cmd_buf;++desc_list[0].paddr_ptr=&data->guest_address;+desc_list[0].len=data->guest_len;+desc_list[0].guest_owned=true;+break;+}+default:+break;+}+}++staticintsnp_map_cmd_buf_desc(structcmd_buf_desc*desc)+{+unsignedlongpaddr;+unsignedintnpages;++if(!desc->len)+return0;++/* Allocate a bounce buffer if this isn't a guest owned page. */+if(!desc->guest_owned){+structpage*page;++page=alloc_pages(GFP_KERNEL_ACCOUNT,get_order(desc->len));+if(!page){+pr_warn("Failed to allocate bounce buffer for SEV legacy command.\n");+return-ENOMEM;+}++desc->paddr_orig=*desc->paddr_ptr;+*desc->paddr_ptr=__psp_pa(page_to_virt(page));+}++paddr=*desc->paddr_ptr;+npages=PAGE_ALIGN(desc->len)>>PAGE_SHIFT;++/* Transition the buffer to firmware-owned. */+if(rmp_mark_pages_firmware(paddr,npages,true)){+pr_warn("Failed move pages to firmware-owned state for SEV legacy command.\n");+return-EFAULT;+}++return0;+}++staticintsnp_unmap_cmd_buf_desc(structcmd_buf_desc*desc)+{+unsignedlongpaddr;+unsignedintnpages;++if(!desc->len)+return0;++paddr=*desc->paddr_ptr;+npages=PAGE_ALIGN(desc->len)>>PAGE_SHIFT;++/* Transition the buffers back to hypervisor-owned. */+if(snp_reclaim_pages(paddr,npages,true)){+pr_warn("Failed to reclaim firmware-owned pages while issuing SEV legacy command.\n");+return-EFAULT;+}++/* Copy data from bounce buffer and then free it. */+if(!desc->guest_owned){+void*bounce_buf=__va(__sme_clr(paddr));+void*dst_buf=__va(__sme_clr(desc->paddr_orig));++memcpy(dst_buf,bounce_buf,desc->len);+__free_pages(virt_to_page(bounce_buf),get_order(desc->len));++/* Restore the original address in the command buffer. */+*desc->paddr_ptr=desc->paddr_orig;+}++return0;+}++staticintsnp_map_cmd_buf_desc_list(intcmd,void*cmd_buf,structcmd_buf_desc*desc_list)+{+inti,n;++snp_populate_cmd_buf_desc_list(cmd,cmd_buf,desc_list);++for(i=0;i<CMD_BUF_DESC_MAX;i++){+structcmd_buf_desc*desc=&desc_list[i];++if(!desc->paddr_ptr)+break;++if(snp_map_cmd_buf_desc(desc))+gotoerr_unmap;+}++return0;++err_unmap:+n=i;+for(i=0;i<n;i++)+snp_unmap_cmd_buf_desc(&desc_list[i]);++return-EFAULT;+}++staticintsnp_unmap_cmd_buf_desc_list(structcmd_buf_desc*desc_list)+{+inti;++for(i=0;i<CMD_BUF_DESC_MAX;i++){+structcmd_buf_desc*desc=&desc_list[i];++if(!desc->paddr_ptr)+break;++if(snp_unmap_cmd_buf_desc(desc))+return-EFAULT;+}++return0;+}++staticboolsev_cmd_buf_writable(intcmd)+{+switch(cmd){+caseSEV_CMD_PLATFORM_STATUS:+caseSEV_CMD_GUEST_STATUS:+caseSEV_CMD_LAUNCH_START:+caseSEV_CMD_RECEIVE_START:+caseSEV_CMD_LAUNCH_MEASURE:+caseSEV_CMD_SEND_START:+caseSEV_CMD_SEND_UPDATE_DATA:+caseSEV_CMD_SEND_UPDATE_VMSA:+caseSEV_CMD_PEK_CSR:+caseSEV_CMD_PDH_CERT_EXPORT:+caseSEV_CMD_GET_ID:+caseSEV_CMD_ATTESTATION_REPORT:+returntrue;+default:+returnfalse;+}+}++/* After SNP is INIT'ed, the behavior of legacy SEV commands is changed. */+staticboolsnp_legacy_handling_needed(intcmd)+{+structsev_device*sev=psp_master->sev_data;++returncmd<SEV_CMD_SNP_INIT&&sev->snp_initialized;+}++staticintsnp_prep_cmd_buf(intcmd,void*cmd_buf,structcmd_buf_desc*desc_list)+{+if(!snp_legacy_handling_needed(cmd))+return0;++if(snp_map_cmd_buf_desc_list(cmd,cmd_buf,desc_list))+return-EFAULT;++/*+*Beforecommandexecution,thecommandbufferneedstobeputinto+*thefirmware-ownedstate.+*/+if(sev_cmd_buf_writable(cmd)){+if(rmp_mark_pages_firmware(__pa(cmd_buf),1,true))+return-EFAULT;+}++return0;+}++staticintsnp_reclaim_cmd_buf(intcmd,void*cmd_buf,structcmd_buf_desc*desc_list)+{+if(!snp_legacy_handling_needed(cmd))+return0;++/*+*Aftercommandcompletion,thecommandbufferneedstobeputback+*intothehypervisor-ownedstate.+*/+if(sev_cmd_buf_writable(cmd))+if(snp_reclaim_pages(__pa(cmd_buf),1,true))+return-EFAULT;++if(snp_unmap_cmd_buf_desc_list(desc_list))+return-EFAULT;++return0;+}+staticint__sev_do_cmd_locked(intcmd,void*data,int*psp_ret){+structcmd_buf_descdesc_list[CMD_BUF_DESC_MAX]={0};structpsp_device*psp=psp_master;structsev_device*sev;unsignedintcmdbuff_hi,cmdbuff_lo;unsignedintphys_lsb,phys_msb;unsignedintreg,ret=0;+void*cmd_buf;intbuf_len;if(!psp||!psp->sev_data)
@@ -527,12 +874,47 @@ static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)*workforsomememory,e.g.vmalloc'daddresses,and@datamaynotbe*physicallycontiguous.*/-if(data)-memcpy(sev->cmd_buf,data,buf_len);+if(data){+/*+*Commandsaregenerallyissuedoneatatimeandrequirethe+*sev_cmd_mutex,buttherecouldberecursivefirmwarerequests+*duetoSEV_CMD_SNP_PAGE_RECLAIMneedingtobeissuedwhile+*preparingbuffersforanothercommand.Thisistheonlyknown+*caseofnestinginthecurrentcode,soexactlyone+*additionalcommandbufferisavailableforthatpurpose.+*/+if(!sev->cmd_buf_active){+cmd_buf=sev->cmd_buf;+sev->cmd_buf_active=true;+}elseif(!sev->cmd_buf_backup_active){+cmd_buf=sev->cmd_buf_backup;+sev->cmd_buf_backup_active=true;+}else{+dev_err(sev->dev,+"SEV: too many firmware commands are in-progress, no command buffers available.\n");+return-EBUSY;+}++memcpy(cmd_buf,data,buf_len);++/*+*ThebehavioroftheSEV-legacycommandsisalteredwhenthe+*SNPfirmwareisintheINITstate.+*/+ret=snp_prep_cmd_buf(cmd,cmd_buf,desc_list);+if(ret){+dev_err(sev->dev,+"SEV: failed to prepare buffer for legacy command %#x. Error: %d\n",+cmd,ret);+returnret;+}+}else{+cmd_buf=sev->cmd_buf;+}/* Get the physical address of the command buffer */-phys_lsb=data?lower_32_bits(__psp_pa(sev->cmd_buf)):0;-phys_msb=data?upper_32_bits(__psp_pa(sev->cmd_buf)):0;+phys_lsb=data?lower_32_bits(__psp_pa(cmd_buf)):0;+phys_msb=data?upper_32_bits(__psp_pa(cmd_buf)):0;dev_dbg(sev->dev,"sev command id %#x buffer 0x%08x%08x timeout %us\n",cmd,phys_msb,phys_lsb,psp_timeout);
@@ -586,15 +968,32 @@ static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)ret=sev_write_init_ex_file_if_required(cmd);}-print_hex_dump_debug("(out): ",DUMP_PREFIX_OFFSET,16,2,data,-buf_len,false);-/**CopypotentialoutputfromthePSPbacktodata.Dothisevenon*failureincasethecallerwantstogleansomethingfromtheerror.*/-if(data)-memcpy(data,sev->cmd_buf,buf_len);+if(data){+/*+*Restorethepagestateafterthecommandcompletes.+*/+ret=snp_reclaim_cmd_buf(cmd,cmd_buf,desc_list);+if(ret){+dev_err(sev->dev,+"SEV: failed to reclaim buffer for legacy command %#x. Error: %d\n",+cmd,ret);+returnret;+}++memcpy(data,cmd_buf,buf_len);++if(sev->cmd_buf_backup_active)+sev->cmd_buf_backup_active=false;+else+sev->cmd_buf_active=false;+}++print_hex_dump_debug("(out): ",DUMP_PREFIX_OFFSET,16,2,data,+buf_len,false);returnret;}
@@ -1696,10 +2095,12 @@ int sev_dev_init(struct psp_device *psp)if(!sev)gotoe_err;-sev->cmd_buf=(void*)devm_get_free_pages(dev,GFP_KERNEL,0);+sev->cmd_buf=(void*)devm_get_free_pages(dev,GFP_KERNEL,1);if(!sev->cmd_buf)gotoe_sev;+sev->cmd_buf_backup=(uint8_t*)sev->cmd_buf+PAGE_SIZE;+psp->sev_data=sev;sev->dev=dev;
From: Michael Roth <hidden> Date: 2023-12-30 16:25:38
From: Ashish Kalra <ashish.kalra@amd.com>
Add a new IOMMU API interface amd_iommu_snp_disable() to transition
IOMMU pages to Hypervisor state from Reclaim state after SNP_SHUTDOWN_EX
command. Invoke this API from the CCP driver after SNP_SHUTDOWN_EX
command.
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 20 +++++++++
drivers/iommu/amd/init.c | 79 ++++++++++++++++++++++++++++++++++++
include/linux/amd-iommu.h | 6 +++
3 files changed, 105 insertions(+)
@@ -3797,3 +3798,81 @@ int amd_iommu_pc_set_reg(struct amd_iommu *iommu, u8 bank, u8 cntr, u8 fxn, u64returniommu_pc_get_set_reg(iommu,bank,cntr,fxn,value,true);}++#ifdef CONFIG_KVM_AMD_SEV+staticintiommu_page_make_shared(void*page)+{+unsignedlongpaddr,pfn;++paddr=iommu_virt_to_phys(page);+/* Cbit maybe set in the paddr */+pfn=__sme_clr(paddr)>>PAGE_SHIFT;++if(!(pfn%PTRS_PER_PMD)){+intret,level;+boolassigned;++ret=snp_lookup_rmpentry(pfn,&assigned,&level);+if(ret)+pr_warn("IOMMU PFN %lx RMP lookup failed, ret %d\n",+pfn,ret);++if(!assigned)+pr_warn("IOMMU PFN %lx not assigned in RMP table\n",+pfn);++if(level>PG_LEVEL_4K){+ret=psmash(pfn);+if(ret){+pr_warn("IOMMU PFN %lx had a huge RMP entry, but attempted psmash failed, ret: %d, level: %d\n",+pfn,ret,level);+}+}+}++returnrmp_make_shared(pfn,PG_LEVEL_4K);+}++staticintiommu_make_shared(void*va,size_tsize)+{+void*page;+intret;++if(!va)+return0;++for(page=va;page<(va+size);page+=PAGE_SIZE){+ret=iommu_page_make_shared(page);+if(ret)+returnret;+}++return0;+}++intamd_iommu_snp_disable(void)+{+structamd_iommu*iommu;+intret;++if(!amd_iommu_snp_en)+return0;++for_each_iommu(iommu){+ret=iommu_make_shared(iommu->evt_buf,EVT_BUFFER_SIZE);+if(ret)+returnret;++ret=iommu_make_shared(iommu->ppr_log,PPR_LOG_SIZE);+if(ret)+returnret;++ret=iommu_make_shared((void*)iommu->cmd_sem,PAGE_SIZE);+if(ret)+returnret;+}++return0;+}+EXPORT_SYMBOL_GPL(amd_iommu_snp_disable);+#endif
From: Michael Roth <hidden> Date: 2023-12-30 16:25:58
From: Brijesh Singh <redacted>
Add support to decrypt guest encrypted memory. These API interfaces can
be used for example to dump VMCBs on SNP guest exit.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: minor commit fixups]
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 32 ++++++++++++++++++++++++++++++++
include/linux/psp-sev.h | 19 +++++++++++++++++++
2 files changed, 51 insertions(+)
@@ -2053,6 +2053,38 @@ int sev_guest_df_flush(int *error)}EXPORT_SYMBOL_GPL(sev_guest_df_flush);+intsnp_guest_dbg_decrypt_page(u64gctx_pfn,u64src_pfn,u64dst_pfn,int*error)+{+structsev_data_snp_dbgdata={0};+structsev_device*sev;+intret;++if(!psp_master||!psp_master->sev_data)+return-ENODEV;++sev=psp_master->sev_data;++if(!sev->snp_initialized)+return-EINVAL;++data.gctx_paddr=sme_me_mask|(gctx_pfn<<PAGE_SHIFT);+data.src_addr=sme_me_mask|(src_pfn<<PAGE_SHIFT);+data.dst_addr=sme_me_mask|(dst_pfn<<PAGE_SHIFT);++/* The destination page must be in the firmware state. */+if(rmp_mark_pages_firmware(data.dst_addr,1,false))+return-EIO;++ret=sev_do_cmd(SEV_CMD_SNP_DBG_DECRYPT,&data,error);++/* Restore the page state */+if(snp_reclaim_pages(data.dst_addr,1,false))+ret=-EIO;++returnret;+}+EXPORT_SYMBOL_GPL(snp_guest_dbg_decrypt_page);+staticvoidsev_exit(structkref*ref){misc_deregister(&misc_dev->misc);
@@ -930,6 +930,20 @@ int sev_guest_decommission(struct sev_data_decommission *data, int *error);*/intsev_do_cmd(intcmd,void*data,int*psp_ret);+/**+*snp_guest_dbg_decrypt_page-performSEVSNP_DBG_DECRYPTcommand+*+*@sev_ret:sevcommandreturncode+*+*Returns:+*0iftheSEVsuccessfullyprocessedthecommand+*-%ENODEViftheSEVdeviceisnotavailable+*-%ENOTSUPPiftheSEVdoesnotsupportSEV+*-%ETIMEDOUTiftheSEVcommandtimedout+*-%EIOiftheSEVreturnedanon-zeroreturncode+*/+intsnp_guest_dbg_decrypt_page(u64gctx_pfn,u64src_pfn,u64dst_pfn,int*error);+void*psp_copy_user_blob(u64uaddr,u32len);void*snp_alloc_firmware_page(gfp_tmask);voidsnp_free_firmware_page(void*addr);
From: Michael Roth <hidden> Date: 2023-12-30 16:26:20
From: Ashish Kalra <ashish.kalra@amd.com>
Add a kdump safe version of sev_firmware_shutdown() registered as a
crash_kexec_post_notifier, which is invoked during panic/crash to do
SEV/SNP shutdown. This is required for transitioning all IOMMU pages
to reclaim/hypervisor state, otherwise re-init of IOMMU pages during
crashdump kernel boot fails and panics the crashdump kernel. This
panic notifier runs in atomic context, hence it ensures not to
acquire any locks/mutexes and polls for PSP command completion
instead of depending on PSP command completion interrupt.
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: remove use of "we" in comments]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/kernel/crash.c | 7 +++
drivers/crypto/ccp/sev-dev.c | 112 +++++++++++++++++++++++++----------
2 files changed, 89 insertions(+), 30 deletions(-)
@@ -144,6 +145,26 @@ static int sev_wait_cmd_ioc(struct sev_device *sev,{intret;+/*+*Ifinvokedduringpanichandling,localinterruptsaredisabled,+*sothePSPcommandcompletioninterruptcan'tbeused.Pollfor+*PSPcommandcompletioninstead.+*/+if(irqs_disabled()){+unsignedlongtimeout_usecs=(timeout*USEC_PER_SEC)/10;++/* Poll for SEV command completion: */+while(timeout_usecs--){+*reg=ioread32(sev->io_regs+sev->vdata->cmdresp_reg);+if(*reg&PSP_CMDRESP_RESP)+return0;++udelay(10);+}++return-ETIMEDOUT;+}+ret=wait_event_timeout(sev->int_queue,sev->int_rcvd,timeout*HZ);if(!ret)
@@ -1358,17 +1379,6 @@ static int __sev_platform_shutdown_locked(int *error)returnret;}-staticintsev_platform_shutdown(int*error)-{-intrc;--mutex_lock(&sev_cmd_mutex);-rc=__sev_platform_shutdown_locked(NULL);-mutex_unlock(&sev_cmd_mutex);--returnrc;-}-staticintsev_get_platform_state(int*state,int*error){structsev_user_data_statusdata;
@@ -1644,7 +1654,7 @@ static int sev_update_firmware(struct device *dev)returnret;}-staticint__sev_snp_shutdown_locked(int*error)+staticint__sev_snp_shutdown_locked(int*error,boolin_panic){structsev_device*sev=psp_master->sev_data;structsev_data_snp_shutdown_exdata;
@@ -1657,7 +1667,16 @@ static int __sev_snp_shutdown_locked(int *error)data.length=sizeof(data);data.iommu_snp_shutdown=1;-wbinvd_on_all_cpus();+/*+*Ifinvokedduringpanichandling,localinterruptsaredisabled+*andallCPUsarestopped,sowbinvd_on_all_cpus()can'tbecalled.+*Inthatcase,awbinvd()isdoneonremoteCPUsviatheNMI+*callback,soonlyalocalwbinvd()isneededhere.+*/+if(!in_panic)+wbinvd_on_all_cpus();+else+wbinvd();ret=__sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN_EX,&data,error);/* SHUTDOWN may require DF_FLUSH */
@@ -1701,17 +1720,6 @@ static int __sev_snp_shutdown_locked(int *error)returnret;}-staticintsev_snp_shutdown(int*error)-{-intrc;--mutex_lock(&sev_cmd_mutex);-rc=__sev_snp_shutdown_locked(error);-mutex_unlock(&sev_cmd_mutex);--returnrc;-}-staticintsev_ioctl_do_pek_import(structsev_issue_cmd*argp,boolwritable){structsev_device*sev=psp_master->sev_data;
@@ -2191,19 +2199,29 @@ int sev_dev_init(struct psp_device *psp)returnret;}-staticvoidsev_firmware_shutdown(structsev_device*sev)+staticvoid__sev_firmware_shutdown(structsev_device*sev,boolin_panic){interror;-sev_platform_shutdown(NULL);+__sev_platform_shutdown_locked(NULL);if(sev_es_tmr){-/* The TMR area was encrypted, flush it from the cache */-wbinvd_on_all_cpus();+/*+*TheTMRareawasencrypted,flushitfromthecache+*+*Ifinvokedduringpanichandling,localinterruptsare+*disabledandallCPUsarestopped,sowbinvd_on_all_cpus()+*can'tbeused.Inthatcase,wbinvd()isdoneonremoteCPUs+*viatheNMIcallback,soalocalwbinvd()issufficienthere.+*/+if(!in_panic)+wbinvd_on_all_cpus();+else+wbinvd();__snp_free_firmware_pages(virt_to_page(sev_es_tmr),get_order(sev_es_tmr_size),-false);+true);sev_es_tmr=NULL;}
From: Michael Roth <hidden> Date: 2023-12-30 16:26:40
From: Brijesh Singh <redacted>
Implement a workaround for an SNP erratum where the CPU will incorrectly
signal an RMP violation #PF if a hugepage (2MB or 1GB) collides with the
RMP entry of a VMCB, VMSA or AVIC backing page.
When SEV-SNP is globally enabled, the CPU marks the VMCB, VMSA, and AVIC
backing pages as "in-use" via a reserved bit in the corresponding RMP
entry after a successful VMRUN. This is done for _all_ VMs, not just
SNP-Active VMs.
If the hypervisor accesses an in-use page through a writable
translation, the CPU will throw an RMP violation #PF. On early SNP
hardware, if an in-use page is 2MB-aligned and software accesses any
part of the associated 2MB region with a hugepage, the CPU will
incorrectly treat the entire 2MB region as in-use and signal a an RMP
violation #PF.
To avoid this, the recommendation is to not use a 2MB-aligned page for
the VMCB, VMSA or AVIC pages. Add a generic allocator that will ensure
that the page returned is not 2MB-aligned and is safe to be used when
SEV-SNP is enabled. Also implement similar handling for the VMCB/VMSA
pages of nested guests.
Co-developed-by: Marc Orr <redacted>
Signed-off-by: Marc Orr <redacted>
Reported-by: Alper Gun <redacted> # for nested VMSA case
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Brijesh Singh <redacted>
Acked-by: Vlastimil Babka <redacted>
[mdr: squash in nested guest handling from Ashish, commit msg fixups]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/kvm-x86-ops.h | 1 +
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/lapic.c | 5 ++++-
arch/x86/kvm/svm/nested.c | 2 +-
arch/x86/kvm/svm/sev.c | 32 ++++++++++++++++++++++++++++++
arch/x86/kvm/svm/svm.c | 17 +++++++++++++---
arch/x86/kvm/svm/svm.h | 1 +
7 files changed, 54 insertions(+), 5 deletions(-)
From: Michael Roth <hidden> Date: 2023-12-30 16:27:01
With all the required host changes in place, it should now be possible
to initialize SNP-related MSR bits, set up RMP table enforcement, and
initialize SNP support in firmware while maintaining legacy support for
running SEV/SEV-ES guests. Go ahead and enable the SNP feature now.
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/disabled-features.h | 4 ++++
1 file changed, 4 insertions(+)
From: Michael Roth <hidden> Date: 2023-12-30 16:27:23
From: Brijesh Singh <redacted>
This command is used to query the SNP platform status. See the SEV-SNP
spec for more details.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
Documentation/virt/coco/sev-guest.rst | 27 ++++++++++++++++
drivers/crypto/ccp/sev-dev.c | 45 +++++++++++++++++++++++++++
include/uapi/linux/psp-sev.h | 1 +
3 files changed, 73 insertions(+)
@@ -67,6 +67,22 @@ counter (e.g. counter overflow), then -EIO will be returned. }; };+The host ioctls are issued to a file descriptor of the /dev/sev device.+The ioctl accepts the command ID/input structure documented below.++::+ struct sev_issue_cmd {+ /* Command ID */+ __u32 cmd;++ /* Command request structure */+ __u64 data;++ /* Firmware error code on failure (see psp-sev.h) */+ __u32 error;+ };++ 2.1 SNP_GET_REPORT ------------------
@@ -124,6 +140,17 @@ be updated with the expected value. See GHCB specification for further detail on how to parse the certificate blob.+2.4 SNP_PLATFORM_STATUS+-----------------------+:Technology: sev-snp+:Type: hypervisor ioctl cmd+:Parameters (out): struct sev_user_data_snp_status+:Returns (out): 0 on success, -negative on error++The SNP_PLATFORM_STATUS command is used to query the SNP platform status. The+status includes API major, minor version and more. See the SEV-SNP+specification for further details.+3. SEV-SNP CPUID Enforcement ============================
@@ -1962,6 +1962,48 @@ static int sev_ioctl_do_pdh_export(struct sev_issue_cmd *argp, bool writable)returnret;}+staticintsev_ioctl_do_snp_platform_status(structsev_issue_cmd*argp)+{+structsev_device*sev=psp_master->sev_data;+structsev_data_snp_addrbuf;+structpage*status_page;+void*data;+intret;++if(!sev->snp_initialized||!argp->data)+return-EINVAL;++status_page=alloc_page(GFP_KERNEL_ACCOUNT);+if(!status_page)+return-ENOMEM;++data=page_address(status_page);+if(rmp_mark_pages_firmware(__pa(data),1,true)){+ret=-EFAULT;+gotocleanup;+}++buf.gctx_paddr=__psp_pa(data);+ret=__sev_do_cmd_locked(SEV_CMD_SNP_PLATFORM_STATUS,&buf,&argp->error);++/* Change the page state before accessing it */+if(snp_reclaim_pages(__pa(data),1,true)){+snp_leak_pages(__pa(data)>>PAGE_SHIFT,1);+return-EFAULT;+}++if(ret)+gotocleanup;++if(copy_to_user((void__user*)argp->data,data,+sizeof(structsev_user_data_snp_status)))+ret=-EFAULT;++cleanup:+__free_pages(status_page,0);+returnret;+}+staticlongsev_ioctl(structfile*file,unsignedintioctl,unsignedlongarg){void__user*argp=(void__user*)arg;
@@ -2013,6 +2055,9 @@ static long sev_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)caseSEV_GET_ID2:ret=sev_ioctl_do_get_id2(&input);break;+caseSNP_PLATFORM_STATUS:+ret=sev_ioctl_do_snp_platform_status(&input);+break;default:ret=-EINVAL;gotoout;
From: Michael Roth <hidden> Date: 2023-12-30 16:27:44
From: Tom Lendacky <thomas.lendacky@amd.com>
The SNP_COMMIT command is used to commit the currently installed version
of the SEV firmware. Once committed, the firmware cannot be replaced
with a previous firmware version (cannot be rolled back). This command
will also update the reported TCB to match that of the currently
installed firmware.
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
[mdr: note the reported TCB update in the documentation/commit]
Signed-off-by: Michael Roth <redacted>
---
Documentation/virt/coco/sev-guest.rst | 11 +++++++++++
drivers/crypto/ccp/sev-dev.c | 17 +++++++++++++++++
include/linux/psp-sev.h | 9 +++++++++
include/uapi/linux/psp-sev.h | 1 +
4 files changed, 38 insertions(+)
@@ -151,6 +151,17 @@ The SNP_PLATFORM_STATUS command is used to query the SNP platform status. The status includes API major, minor version and more. See the SEV-SNP specification for further details.+2.5 SNP_COMMIT+--------------+:Technology: sev-snp+:Type: hypervisor ioctl cmd+:Returns (out): 0 on success, -negative on error++SNP_COMMIT is used to commit the currently installed firmware using the+SEV-SNP firmware SNP_COMMIT command. This prevents roll-back to a previously+committed firmware version. This will also update the reported TCB to match+that of the currently installed firmware.+3. SEV-SNP CPUID Enforcement ============================
@@ -224,6 +224,7 @@ static int sev_cmd_buffer_len(int cmd)caseSEV_CMD_SNP_PLATFORM_STATUS:returnsizeof(structsev_data_snp_addr);caseSEV_CMD_SNP_GUEST_REQUEST:returnsizeof(structsev_data_snp_guest_request);caseSEV_CMD_SNP_CONFIG:returnsizeof(structsev_user_data_snp_config);+caseSEV_CMD_SNP_COMMIT:returnsizeof(structsev_data_snp_commit);default:return0;}
@@ -2004,6 +2005,19 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp)returnret;}+staticintsev_ioctl_do_snp_commit(structsev_issue_cmd*argp)+{+structsev_device*sev=psp_master->sev_data;+structsev_data_snp_commitbuf;++if(!sev->snp_initialized)+return-EINVAL;++buf.length=sizeof(buf);++return__sev_do_cmd_locked(SEV_CMD_SNP_COMMIT,&buf,&argp->error);+}+staticlongsev_ioctl(structfile*file,unsignedintioctl,unsignedlongarg){void__user*argp=(void__user*)arg;
@@ -2058,6 +2072,9 @@ static long sev_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)caseSNP_PLATFORM_STATUS:ret=sev_ioctl_do_snp_platform_status(&input);break;+caseSNP_COMMIT:+ret=sev_ioctl_do_snp_commit(&input);+break;default:ret=-EINVAL;gotoout;
From: Michael Roth <hidden> Date: 2023-12-30 16:28:06
From: Brijesh Singh <redacted>
The SEV-SNP firmware provides the SNP_CONFIG command used to set various
system-wide configuration values for SNP guests, such as the TCB version
to be reported in guest attestation reports. Add an interface to set
this via userspace.
Co-developed-by: Alexey Kardashevskiy <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
Co-developed-by: Dionna Glaze <redacted>
Signed-off-by: Dionna Glaze <redacted>
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: squash in doc patch from Dionna, drop extended request/certificate
handling and simplify this to a simple wrapper around SNP_CONFIG fw
cmd]
Signed-off-by: Michael Roth <redacted>
---
Documentation/virt/coco/sev-guest.rst | 13 +++++++++++++
drivers/crypto/ccp/sev-dev.c | 20 ++++++++++++++++++++
include/uapi/linux/psp-sev.h | 1 +
3 files changed, 34 insertions(+)
@@ -162,6 +162,19 @@ SEV-SNP firmware SNP_COMMIT command. This prevents roll-back to a previously committed firmware version. This will also update the reported TCB to match that of the currently installed firmware.+2.6 SNP_SET_CONFIG+------------------+:Technology: sev-snp+:Type: hypervisor ioctl cmd+:Parameters (in): struct sev_user_data_snp_config+:Returns (out): 0 on success, -negative on error++The SNP_SET_CONFIG is used to set the system-wide configuration such as+reported TCB version in the attestation report. The command is similar to+SNP_CONFIG command defined in the SEV-SNP spec. The current values of the+firmware parameters affected by this command can be queried via+SNP_PLATFORM_STATUS.+3. SEV-SNP CPUID Enforcement ============================
@@ -2018,6 +2018,23 @@ static int sev_ioctl_do_snp_commit(struct sev_issue_cmd *argp)return__sev_do_cmd_locked(SEV_CMD_SNP_COMMIT,&buf,&argp->error);}+staticintsev_ioctl_do_snp_set_config(structsev_issue_cmd*argp,boolwritable)+{+structsev_device*sev=psp_master->sev_data;+structsev_user_data_snp_configconfig;++if(!sev->snp_initialized||!argp->data)+return-EINVAL;++if(!writable)+return-EPERM;++if(copy_from_user(&config,(void__user*)argp->data,sizeof(config)))+return-EFAULT;++return__sev_do_cmd_locked(SEV_CMD_SNP_CONFIG,&config,&argp->error);+}+staticlongsev_ioctl(structfile*file,unsignedintioctl,unsignedlongarg){void__user*argp=(void__user*)arg;
@@ -2075,6 +2092,9 @@ static long sev_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)caseSNP_COMMIT:ret=sev_ioctl_do_snp_commit(&input);break;+caseSNP_SET_CONFIG:+ret=sev_ioctl_do_snp_set_config(&input,writable);+break;default:ret=-EINVAL;gotoout;
From: Michael Roth <hidden> Date: 2023-12-30 16:29:09
From: Brijesh Singh <redacted>
Add CPU feature detection for Secure Encrypted Virtualization with
Secure Nested Paging. This feature adds a strong memory integrity
protection to help prevent malicious hypervisor-based attacks like
data replay, memory re-mapping, and more.
Since enabling the SNP CPU feature imposes a number of additional
requirements on host initialization and handling legacy firmware APIs
for SEV/SEV-ES guests, only introduce the CPU feature bit so that the
relevant handling can be added, but leave it disabled via a
disabled-features mask.
Once all the necessary changes needed to maintain legacy SEV/SEV-ES
support are introduced in subsequent patches, the SNP feature bit will
be unmasked/enabled.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Jarkko Sakkinen <redacted>
Signed-off-by: Ashish Kalra <Ashish.Kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/include/asm/disabled-features.h | 4 +++-
arch/x86/kernel/cpu/amd.c | 5 +++--
tools/arch/x86/include/asm/cpufeatures.h | 1 +
4 files changed, 8 insertions(+), 3 deletions(-)
From: Michael Roth <hidden> Date: 2023-12-30 16:29:35
From: Kim Phillips <redacted>
Without SEV-SNP, Automatic IBRS protects only the kernel. But when
SEV-SNP is enabled, the Automatic IBRS protection umbrella widens to all
host-side code, including userspace. This protection comes at a cost:
reduced userspace indirect branch performance.
To avoid this performance loss, don't use Automatic IBRS on SEV-SNP
hosts. Fall back to retpolines instead.
Signed-off-by: Kim Phillips <redacted>
Acked-by: Dave Hansen <redacted>
[mdr: squash in changes from review discussion]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/kernel/cpu/common.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
From: Michael Roth <hidden> Date: 2023-12-30 16:29:56
From: Ashish Kalra <ashish.kalra@amd.com>
Currently the expectation is that the kernel will call
amd_iommu_snp_enable() to perform various checks and set the
amd_iommu_snp_en flag that the IOMMU uses to adjust its setup routines
to account for additional requirements on hosts where SNP is enabled.
This is somewhat fragile as it relies on this call being done prior to
IOMMU setup. It is more robust to just do this automatically as part of
IOMMU initialization, so rework the code accordingly.
There is still a need to export information about whether or not the
IOMMU is configured in a manner compatible with SNP, so relocate the
existing amd_iommu_snp_en flag so it can be used to convey that
information in place of the return code that was previously provided by
calls to amd_iommu_snp_enable().
While here, also adjust the kernel messages related to IOMMU SNP
enablement for consistency/grammar/clarity.
Suggested-by: Borislav Petkov (AMD) <bp@alien8.de>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Co-developed-by: Michael Roth <redacted>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/iommu.h | 1 +
drivers/iommu/amd/amd_iommu.h | 1 -
drivers/iommu/amd/init.c | 69 ++++++++++++++++-------------------
include/linux/amd-iommu.h | 4 --
4 files changed, 32 insertions(+), 43 deletions(-)
@@ -3221,6 +3221,36 @@ static bool __init detect_ivrs(void)returntrue;}+staticvoidiommu_snp_enable(void)+{+#ifdef CONFIG_KVM_AMD_SEV+if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return;+/*+*TheSNPsupportrequiresthatIOMMUmustbeenabled,andis+*notconfiguredinthepassthroughmode.+*/+if(no_iommu||iommu_default_passthrough()){+pr_err("SNP: IOMMU is disabled or configured in passthrough mode, SNP cannot be supported.\n");+return;+}++amd_iommu_snp_en=check_feature(FEATURE_SNP);+if(!amd_iommu_snp_en){+pr_err("SNP: IOMMU SNP feature is not enabled, SNP cannot be supported.\n");+return;+}++pr_info("IOMMU SNP support is enabled.\n");++/* Enforce IOMMU v1 pagetable when SNP is enabled. */+if(amd_iommu_pgtable!=AMD_IOMMU_V1){+pr_warn("Forcing use of AMD IOMMU v1 page table due to SNP.\n");+amd_iommu_pgtable=AMD_IOMMU_V1;+}+#endif+}+/******************************************************************************AMDIOMMUInitializationStateMachine
@@ -3256,6 +3286,7 @@ static int __init state_next(void)break;caseIOMMU_ENABLED:register_syscore_ops(&amd_iommu_syscore_ops);+iommu_snp_enable();ret=amd_iommu_init_pci();init_state=ret?IOMMU_INIT_ERROR:IOMMU_PCI_INIT;break;
@@ -3766,41 +3797,3 @@ int amd_iommu_pc_set_reg(struct amd_iommu *iommu, u8 bank, u8 cntr, u8 fxn, u64returniommu_pc_get_set_reg(iommu,bank,cntr,fxn,value,true);}--#ifdef CONFIG_AMD_MEM_ENCRYPT-intamd_iommu_snp_enable(void)-{-/*-*TheSNPsupportrequiresthatIOMMUmustbeenabled,andis-*notconfiguredinthepassthroughmode.-*/-if(no_iommu||iommu_default_passthrough()){-pr_err("SNP: IOMMU is disabled or configured in passthrough mode, SNP cannot be supported");-return-EINVAL;-}--/*-*PreventenablingSNPafterIOMMU_ENABLEDstatebecausethisprocess-*affecthowIOMMUdriversetsupdatastructuresandconfigures-*IOMMUhardware.-*/-if(init_state>IOMMU_ENABLED){-pr_err("SNP: Too late to enable SNP for IOMMU.\n");-return-EINVAL;-}--amd_iommu_snp_en=check_feature(FEATURE_SNP);-if(!amd_iommu_snp_en)-return-EINVAL;--pr_info("SNP enabled\n");--/* Enforce IOMMU v1 pagetable when SNP is enabled. */-if(amd_iommu_pgtable!=AMD_IOMMU_V1){-pr_warn("Force to using AMD IOMMU v1 page table due to SNP\n");-amd_iommu_pgtable=AMD_IOMMU_V1;-}--return0;-}-#endif
From: Michael Roth <hidden> Date: 2023-12-30 16:30:14
From: Brijesh Singh <redacted>
The memory integrity guarantees of SEV-SNP are enforced through a new
structure called the Reverse Map Table (RMP). The RMP is a single data
structure shared across the system that contains one entry for every 4K
page of DRAM that may be used by SEV-SNP VMs. APM2 section 15.36 details
a number of steps needed to detect/enable SEV-SNP and RMP table support
on the host:
- Detect SEV-SNP support based on CPUID bit
- Initialize the RMP table memory reported by the RMP base/end MSR
registers and configure IOMMU to be compatible with RMP access
restrictions
- Set the MtrrFixDramModEn bit in SYSCFG MSR
- Set the SecureNestedPagingEn and VMPLEn bits in the SYSCFG MSR
- Configure IOMMU
RMP table entry format is non-architectural and it can vary by
processor. It is defined by the PPR. Restrict SNP support to CPU
models/families which are compatible with the current RMP table entry
format to guard against any undefined behavior when running on other
system types. Future models/support will handle this through an
architectural mechanism to allow for broader compatibility.
SNP host code depends on CONFIG_KVM_AMD_SEV config flag, which may be
enabled even when CONFIG_AMD_MEM_ENCRYPT isn't set, so update the
SNP-specific IOMMU helpers used here to rely on CONFIG_KVM_AMD_SEV
instead of CONFIG_AMD_MEM_ENCRYPT.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Co-developed-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Co-developed-by: Michael Roth <redacted>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/Kbuild | 2 +
arch/x86/include/asm/msr-index.h | 11 +-
arch/x86/include/asm/sev.h | 6 +
arch/x86/kernel/cpu/amd.c | 15 +++
arch/x86/virt/svm/Makefile | 3 +
arch/x86/virt/svm/sev.c | 219 +++++++++++++++++++++++++++++++
6 files changed, 255 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/virt/svm/Makefile
create mode 100644 arch/x86/virt/svm/sev.c
@@ -0,0 +1,219 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*AMDSVM-SEVHostSupport.+*+*Copyright(C)2023AdvancedMicroDevices,Inc.+*+*Author:AshishKalra<ashish.kalra@amd.com>+*+*/++#include<linux/cc_platform.h>+#include<linux/printk.h>+#include<linux/mm_types.h>+#include<linux/set_memory.h>+#include<linux/memblock.h>+#include<linux/kernel.h>+#include<linux/mm.h>+#include<linux/cpumask.h>+#include<linux/iommu.h>+#include<linux/amd-iommu.h>++#include<asm/sev.h>+#include<asm/processor.h>+#include<asm/setup.h>+#include<asm/svm.h>+#include<asm/smp.h>+#include<asm/cpu.h>+#include<asm/apic.h>+#include<asm/cpuid.h>+#include<asm/cmdline.h>+#include<asm/iommu.h>++/*+*TheRMPentryformatisnotarchitectural.TheformatisdefinedinPPR+*Family19hModel01h,RevB1processor.+*/+structrmpentry{+u64assigned:1,+pagesize:1,+immutable:1,+rsvd1:9,+gpa:39,+asid:10,+vmsa:1,+validated:1,+rsvd2:1;+u64rsvd3;+}__packed;++/*+*Thefirst16KBfromtheRMP_BASEisusedbytheprocessorforthe+*bookkeeping,therangeneedstobeaddedduringtheRMPentrylookup.+*/+#define RMPTABLE_CPU_BOOKKEEPING_SZ 0x4000++staticu64probed_rmp_base,probed_rmp_size;+staticstructrmpentry*rmptable__ro_after_init;+staticu64rmptable_max_pfn__ro_after_init;++#undef pr_fmt+#define pr_fmt(fmt) "SEV-SNP: " fmt++staticint__mfd_enable(unsignedintcpu)+{+u64val;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return0;++rdmsrl(MSR_AMD64_SYSCFG,val);++val|=MSR_AMD64_SYSCFG_MFDM;++wrmsrl(MSR_AMD64_SYSCFG,val);++return0;+}++static__initvoidmfd_enable(void*arg)+{+__mfd_enable(smp_processor_id());+}++staticint__snp_enable(unsignedintcpu)+{+u64val;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return0;++rdmsrl(MSR_AMD64_SYSCFG,val);++val|=MSR_AMD64_SYSCFG_SNP_EN;+val|=MSR_AMD64_SYSCFG_SNP_VMPL_EN;++wrmsrl(MSR_AMD64_SYSCFG,val);++return0;+}++static__initvoidsnp_enable(void*arg)+{+__snp_enable(smp_processor_id());+}++#define RMP_ADDR_MASK GENMASK_ULL(51, 13)++boolsnp_probe_rmptable_info(void)+{+u64max_rmp_pfn,calc_rmp_sz,rmp_sz,rmp_base,rmp_end;++rdmsrl(MSR_AMD64_RMP_BASE,rmp_base);+rdmsrl(MSR_AMD64_RMP_END,rmp_end);++if(!(rmp_base&RMP_ADDR_MASK)||!(rmp_end&RMP_ADDR_MASK)){+pr_err("Memory for the RMP table has not been reserved by BIOS\n");+returnfalse;+}++if(rmp_base>rmp_end){+pr_err("RMP configuration not valid: base=%#llx, end=%#llx\n",rmp_base,rmp_end);+returnfalse;+}++rmp_sz=rmp_end-rmp_base+1;++/*+*CalculatetheamountthememorythatmustbereservedbytheBIOSto+*addressthewholeRAM,includingthebookkeepingarea.TheRMPitself+*mustalsobecovered.+*/+max_rmp_pfn=max_pfn;+if(PHYS_PFN(rmp_end)>max_pfn)+max_rmp_pfn=PHYS_PFN(rmp_end);++calc_rmp_sz=(max_rmp_pfn<<4)+RMPTABLE_CPU_BOOKKEEPING_SZ;++if(calc_rmp_sz>rmp_sz){+pr_err("Memory reserved for the RMP table does not cover full system RAM (expected 0x%llx got 0x%llx)\n",+calc_rmp_sz,rmp_sz);+returnfalse;+}++probed_rmp_base=rmp_base;+probed_rmp_size=rmp_sz;++pr_info("RMP table physical range [0x%016llx - 0x%016llx]\n",+probed_rmp_base,probed_rmp_base+probed_rmp_size-1);++returntrue;+}++staticint__init__snp_rmptable_init(void)+{+u64rmptable_size;+void*rmptable_start;+u64val;++if(!probed_rmp_size)+return1;++rmptable_start=memremap(probed_rmp_base,probed_rmp_size,MEMREMAP_WB);+if(!rmptable_start){+pr_err("Failed to map RMP table\n");+return1;+}++/*+*CheckifSEV-SNPisalreadyenabled,thiscanhappenincaseof+*kexecboot.+*/+rdmsrl(MSR_AMD64_SYSCFG,val);+if(val&MSR_AMD64_SYSCFG_SNP_EN)+gotoskip_enable;++memset(rmptable_start,0,probed_rmp_size);++/* Flush the caches to ensure that data is written before SNP is enabled. */+wbinvd_on_all_cpus();++/* MtrrFixDramModEn must be enabled on all the CPUs prior to enabling SNP. */+on_each_cpu(mfd_enable,NULL,1);++on_each_cpu(snp_enable,NULL,1);++skip_enable:+rmptable_start+=RMPTABLE_CPU_BOOKKEEPING_SZ;+rmptable_size=probed_rmp_size-RMPTABLE_CPU_BOOKKEEPING_SZ;++rmptable=(structrmpentry*)rmptable_start;+rmptable_max_pfn=rmptable_size/sizeof(structrmpentry)-1;++return0;+}++staticint__initsnp_rmptable_init(void)+{+if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return0;++if(!amd_iommu_snp_en)+return0;++if(__snp_rmptable_init())+gotonosnp;++cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,"x86/rmptable_init:online",__snp_enable,NULL);++return0;++nosnp:+setup_clear_cpu_cap(X86_FEATURE_SEV_SNP);+return-ENOSYS;+}++/*+*ThismustbecalledaftertheIOMMUhasbeeninitialized.+*/+device_initcall(snp_rmptable_init);
From: Michael Roth <hidden> Date: 2023-12-30 16:30:34
From: Ashish Kalra <ashish.kalra@amd.com>
SNP enabled platforms require the MtrrFixDramModeEn bit to be set across
all CPUs when SNP is enabled. Therefore, don't print error messages when
MtrrFixDramModeEn is set when bringing CPUs online.
Reported-by: Jeremi Piotrowski <redacted>
Closes: https://lore.kernel.org/kvm/68b2d6bf-bce7-47f9-bebb-2652cc923ff9@linux.microsoft.com/
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/kernel/cpu/mtrr/generic.c | 3 +++
1 file changed, 3 insertions(+)
On Sat, Dec 30, 2023 at 10:19:29AM -0600, Michael Roth wrote:
quoted hunk
From: Brijesh Singh <redacted>
Add CPU feature detection for Secure Encrypted Virtualization with
Secure Nested Paging. This feature adds a strong memory integrity
protection to help prevent malicious hypervisor-based attacks like
data replay, memory re-mapping, and more.
Since enabling the SNP CPU feature imposes a number of additional
requirements on host initialization and handling legacy firmware APIs
for SEV/SEV-ES guests, only introduce the CPU feature bit so that the
relevant handling can be added, but leave it disabled via a
disabled-features mask.
Once all the necessary changes needed to maintain legacy SEV/SEV-ES
support are introduced in subsequent patches, the SNP feature bit will
be unmasked/enabled.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Jarkko Sakkinen <redacted>
Signed-off-by: Ashish Kalra <Ashish.Kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/include/asm/disabled-features.h | 4 +++-
arch/x86/kernel/cpu/amd.c | 5 +++--
tools/arch/x86/include/asm/cpufeatures.h | 1 +
4 files changed, 8 insertions(+), 3 deletions(-)
From: Michael Roth <hidden> Date: 2023-12-31 16:45:01
On Sun, Dec 31, 2023 at 12:50:12PM +0100, Borislav Petkov wrote:
quoted hunk
On Sat, Dec 30, 2023 at 10:19:29AM -0600, Michael Roth wrote:
quoted
From: Brijesh Singh <redacted>
Add CPU feature detection for Secure Encrypted Virtualization with
Secure Nested Paging. This feature adds a strong memory integrity
protection to help prevent malicious hypervisor-based attacks like
data replay, memory re-mapping, and more.
Since enabling the SNP CPU feature imposes a number of additional
requirements on host initialization and handling legacy firmware APIs
for SEV/SEV-ES guests, only introduce the CPU feature bit so that the
relevant handling can be added, but leave it disabled via a
disabled-features mask.
Once all the necessary changes needed to maintain legacy SEV/SEV-ES
support are introduced in subsequent patches, the SNP feature bit will
be unmasked/enabled.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Jarkko Sakkinen <redacted>
Signed-off-by: Ashish Kalra <Ashish.Kalra@amd.com>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/include/asm/disabled-features.h | 4 +++-
arch/x86/kernel/cpu/amd.c | 5 +++--
tools/arch/x86/include/asm/cpufeatures.h | 1 +
4 files changed, 8 insertions(+), 3 deletions(-)
Sorry, I must have inverted things when I was squashing in the changes =\
I've gone ahead and force-pushed your fixup to the snp-host-init-v1
branch.
Thanks,
Mike
On Sat, Dec 30, 2023 at 10:19:31AM -0600, Michael Roth wrote:
quoted hunk
+static void iommu_snp_enable(void)+{+#ifdef CONFIG_KVM_AMD_SEV+ if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+ return;+ /*+ * The SNP support requires that IOMMU must be enabled, and is+ * not configured in the passthrough mode.+ */+ if (no_iommu || iommu_default_passthrough()) {+ pr_err("SNP: IOMMU is disabled or configured in passthrough mode, SNP cannot be supported.\n");+ return;+ }++ amd_iommu_snp_en = check_feature(FEATURE_SNP);+ if (!amd_iommu_snp_en) {+ pr_err("SNP: IOMMU SNP feature is not enabled, SNP cannot be supported.\n");+ return;+ }++ pr_info("IOMMU SNP support is enabled.\n");++ /* Enforce IOMMU v1 pagetable when SNP is enabled. */+ if (amd_iommu_pgtable != AMD_IOMMU_V1) {+ pr_warn("Forcing use of AMD IOMMU v1 page table due to SNP.\n");+ amd_iommu_pgtable = AMD_IOMMU_V1;+ }
Kernel code usually says simple "<bla> enabled" not "<bla> is enabled".
Other than that, LGTM.
---
@@ -3231,17 +3231,17 @@ static void iommu_snp_enable(void)*notconfiguredinthepassthroughmode.*/if(no_iommu||iommu_default_passthrough()){-pr_err("SNP: IOMMU is disabled or configured in passthrough mode, SNP cannot be supported.\n");+pr_err("SNP: IOMMU disabled or configured in passthrough mode, SNP cannot be supported.\n");return;}amd_iommu_snp_en=check_feature(FEATURE_SNP);if(!amd_iommu_snp_en){-pr_err("SNP: IOMMU SNP feature is not enabled, SNP cannot be supported.\n");+pr_err("SNP: IOMMU SNP feature not enabled, SNP cannot be supported.\n");return;}-pr_info("IOMMU SNP support is enabled.\n");+pr_info("IOMMU SNP support enabled.\n");/* Enforce IOMMU v1 pagetable when SNP is enabled. */if(amd_iommu_pgtable!=AMD_IOMMU_V1){
On Sat, Dec 30, 2023 at 10:19:31AM -0600, Michael Roth wrote:
From: Ashish Kalra <ashish.kalra@amd.com>
Currently the expectation is that the kernel will call
amd_iommu_snp_enable() to perform various checks and set the
amd_iommu_snp_en flag that the IOMMU uses to adjust its setup routines
to account for additional requirements on hosts where SNP is enabled.
This is somewhat fragile as it relies on this call being done prior to
IOMMU setup. It is more robust to just do this automatically as part of
IOMMU initialization, so rework the code accordingly.
There is still a need to export information about whether or not the
IOMMU is configured in a manner compatible with SNP, so relocate the
existing amd_iommu_snp_en flag so it can be used to convey that
information in place of the return code that was previously provided by
calls to amd_iommu_snp_enable().
While here, also adjust the kernel messages related to IOMMU SNP
enablement for consistency/grammar/clarity.
Suggested-by: Borislav Petkov (AMD) <bp@alien8.de>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Co-developed-by: Michael Roth <redacted>
Signed-off-by: Michael Roth <redacted>
Acked-by: Joerg Roedel <redacted>
--
Jörg Rödel
jroedel@suse.de
SUSE Software Solutions Germany GmbH
Frankenstraße 146
90461 Nürnberg
Germany
(HRB 36809, AG Nürnberg)
Geschäftsführer: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman
From: Brijesh Singh <redacted>
The memory integrity guarantees of SEV-SNP are enforced through a new
structure called the Reverse Map Table (RMP). The RMP is a single data
structure shared across the system that contains one entry for every 4K
page of DRAM that may be used by SEV-SNP VMs. APM2 section 15.36 details
a number of steps needed to detect/enable SEV-SNP and RMP table support
on the host:
- Detect SEV-SNP support based on CPUID bit
- Initialize the RMP table memory reported by the RMP base/end MSR
registers and configure IOMMU to be compatible with RMP access
restrictions
- Set the MtrrFixDramModEn bit in SYSCFG MSR
- Set the SecureNestedPagingEn and VMPLEn bits in the SYSCFG MSR
- Configure IOMMU
RMP table entry format is non-architectural and it can vary by
processor. It is defined by the PPR. Restrict SNP support to CPU
models/families which are compatible with the current RMP table entry
format to guard against any undefined behavior when running on other
system types. Future models/support will handle this through an
architectural mechanism to allow for broader compatibility.
SNP host code depends on CONFIG_KVM_AMD_SEV config flag, which may be
enabled even when CONFIG_AMD_MEM_ENCRYPT isn't set, so update the
SNP-specific IOMMU helpers used here to rely on CONFIG_KVM_AMD_SEV
instead of CONFIG_AMD_MEM_ENCRYPT.
Signed-off-by: Brijesh Singh <redacted>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Co-developed-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Co-developed-by: Michael Roth <redacted>
Signed-off-by: Michael Roth <redacted>
---
arch/x86/Kbuild | 2 +
arch/x86/include/asm/msr-index.h | 11 +-
arch/x86/include/asm/sev.h | 6 +
arch/x86/kernel/cpu/amd.c | 15 +++
arch/x86/virt/svm/Makefile | 3 +
arch/x86/virt/svm/sev.c | 219 +++++++++++++++++++++++++++++++
6 files changed, 255 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/virt/svm/Makefile
create mode 100644 arch/x86/virt/svm/sev.c
Is there a really good reason to perform the snp_probe_smptable_info() check at this
point (instead of in snp_rmptable_init). snp_rmptable_init will also clear the cap
on failure, and bsp_init_amd() runs too early to allow for the kernel to allocate the
rmptable itself. I pointed out in the previous review that kernel allocation of rmptable
is necessary in SNP-host capable VMs in Azure.
@@ -0,0 +1,219 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*AMDSVM-SEVHostSupport.+*+*Copyright(C)2023AdvancedMicroDevices,Inc.+*+*Author:AshishKalra<ashish.kalra@amd.com>+*+*/++#include<linux/cc_platform.h>+#include<linux/printk.h>+#include<linux/mm_types.h>+#include<linux/set_memory.h>+#include<linux/memblock.h>+#include<linux/kernel.h>+#include<linux/mm.h>+#include<linux/cpumask.h>+#include<linux/iommu.h>+#include<linux/amd-iommu.h>++#include<asm/sev.h>+#include<asm/processor.h>+#include<asm/setup.h>+#include<asm/svm.h>+#include<asm/smp.h>+#include<asm/cpu.h>+#include<asm/apic.h>+#include<asm/cpuid.h>+#include<asm/cmdline.h>+#include<asm/iommu.h>++/*+*TheRMPentryformatisnotarchitectural.TheformatisdefinedinPPR+*Family19hModel01h,RevB1processor.+*/+structrmpentry{+u64assigned:1,+pagesize:1,+immutable:1,+rsvd1:9,+gpa:39,+asid:10,+vmsa:1,+validated:1,+rsvd2:1;+u64rsvd3;+}__packed;++/*+*Thefirst16KBfromtheRMP_BASEisusedbytheprocessorforthe+*bookkeeping,therangeneedstobeaddedduringtheRMPentrylookup.+*/+#define RMPTABLE_CPU_BOOKKEEPING_SZ 0x4000++staticu64probed_rmp_base,probed_rmp_size;+staticstructrmpentry*rmptable__ro_after_init;+staticu64rmptable_max_pfn__ro_after_init;++#undef pr_fmt+#define pr_fmt(fmt) "SEV-SNP: " fmt++staticint__mfd_enable(unsignedintcpu)+{+u64val;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return0;++rdmsrl(MSR_AMD64_SYSCFG,val);++val|=MSR_AMD64_SYSCFG_MFDM;++wrmsrl(MSR_AMD64_SYSCFG,val);++return0;+}++static__initvoidmfd_enable(void*arg)+{+__mfd_enable(smp_processor_id());+}++staticint__snp_enable(unsignedintcpu)+{+u64val;++if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return0;++rdmsrl(MSR_AMD64_SYSCFG,val);++val|=MSR_AMD64_SYSCFG_SNP_EN;+val|=MSR_AMD64_SYSCFG_SNP_VMPL_EN;++wrmsrl(MSR_AMD64_SYSCFG,val);++return0;+}++static__initvoidsnp_enable(void*arg)+{+__snp_enable(smp_processor_id());+}++#define RMP_ADDR_MASK GENMASK_ULL(51, 13)++boolsnp_probe_rmptable_info(void)+{+u64max_rmp_pfn,calc_rmp_sz,rmp_sz,rmp_base,rmp_end;++rdmsrl(MSR_AMD64_RMP_BASE,rmp_base);+rdmsrl(MSR_AMD64_RMP_END,rmp_end);++if(!(rmp_base&RMP_ADDR_MASK)||!(rmp_end&RMP_ADDR_MASK)){+pr_err("Memory for the RMP table has not been reserved by BIOS\n");+returnfalse;+}++if(rmp_base>rmp_end){+pr_err("RMP configuration not valid: base=%#llx, end=%#llx\n",rmp_base,rmp_end);+returnfalse;+}++rmp_sz=rmp_end-rmp_base+1;++/*+*CalculatetheamountthememorythatmustbereservedbytheBIOSto+*addressthewholeRAM,includingthebookkeepingarea.TheRMPitself+*mustalsobecovered.+*/+max_rmp_pfn=max_pfn;+if(PHYS_PFN(rmp_end)>max_pfn)+max_rmp_pfn=PHYS_PFN(rmp_end);++calc_rmp_sz=(max_rmp_pfn<<4)+RMPTABLE_CPU_BOOKKEEPING_SZ;++if(calc_rmp_sz>rmp_sz){+pr_err("Memory reserved for the RMP table does not cover full system RAM (expected 0x%llx got 0x%llx)\n",+calc_rmp_sz,rmp_sz);+returnfalse;+}++probed_rmp_base=rmp_base;+probed_rmp_size=rmp_sz;++pr_info("RMP table physical range [0x%016llx - 0x%016llx]\n",+probed_rmp_base,probed_rmp_base+probed_rmp_size-1);++returntrue;+}++staticint__init__snp_rmptable_init(void)+{+u64rmptable_size;+void*rmptable_start;+u64val;++if(!probed_rmp_size)+return1;++rmptable_start=memremap(probed_rmp_base,probed_rmp_size,MEMREMAP_WB);+if(!rmptable_start){+pr_err("Failed to map RMP table\n");+return1;+}++/*+*CheckifSEV-SNPisalreadyenabled,thiscanhappenincaseof+*kexecboot.+*/+rdmsrl(MSR_AMD64_SYSCFG,val);+if(val&MSR_AMD64_SYSCFG_SNP_EN)+gotoskip_enable;++memset(rmptable_start,0,probed_rmp_size);++/* Flush the caches to ensure that data is written before SNP is enabled. */+wbinvd_on_all_cpus();++/* MtrrFixDramModEn must be enabled on all the CPUs prior to enabling SNP. */+on_each_cpu(mfd_enable,NULL,1);++on_each_cpu(snp_enable,NULL,1);++skip_enable:+rmptable_start+=RMPTABLE_CPU_BOOKKEEPING_SZ;+rmptable_size=probed_rmp_size-RMPTABLE_CPU_BOOKKEEPING_SZ;++rmptable=(structrmpentry*)rmptable_start;+rmptable_max_pfn=rmptable_size/sizeof(structrmpentry)-1;++return0;+}++staticint__initsnp_rmptable_init(void)+{+if(!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+return0;++if(!amd_iommu_snp_en)+return0;
Looks better - do you think it'll be OK to add a X86_FEATURE_HYPERVISOR check at this point
later to account for SNP-host capable VMs with no access to an iommu?
Jeremi
quoted hunk
++ if (__snp_rmptable_init())+ goto nosnp;++ cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/rmptable_init:online", __snp_enable, NULL);++ return 0;++nosnp:+ setup_clear_cpu_cap(X86_FEATURE_SEV_SNP);+ return -ENOSYS;+}++/*+ * This must be called after the IOMMU has been initialized.+ */+device_initcall(snp_rmptable_init);
On Sat, Dec 30, 2023 at 10:19:32AM -0600, Michael Roth wrote:
From: Brijesh Singh <redacted>
The memory integrity guarantees of SEV-SNP are enforced through a new
structure called the Reverse Map Table (RMP). The RMP is a single data
structure shared across the system that contains one entry for every 4K
page of DRAM that may be used by SEV-SNP VMs. APM2 section 15.36 details
a number of steps needed to detect/enable SEV-SNP and RMP table support
on the host:
- Detect SEV-SNP support based on CPUID bit
- Initialize the RMP table memory reported by the RMP base/end MSR
registers and configure IOMMU to be compatible with RMP access
restrictions
- Set the MtrrFixDramModEn bit in SYSCFG MSR
- Set the SecureNestedPagingEn and VMPLEn bits in the SYSCFG MSR
- Configure IOMMU
RMP table entry format is non-architectural and it can vary by
processor. It is defined by the PPR. Restrict SNP support to CPU
models/families which are compatible with the current RMP table entry
format to guard against any undefined behavior when running on other
system types. Future models/support will handle this through an
architectural mechanism to allow for broader compatibility.
SNP host code depends on CONFIG_KVM_AMD_SEV config flag, which may be
enabled even when CONFIG_AMD_MEM_ENCRYPT isn't set, so update the
SNP-specific IOMMU helpers used here to rely on CONFIG_KVM_AMD_SEV
instead of CONFIG_AMD_MEM_ENCRYPT.
Small fixups to the commit message:
The memory integrity guarantees of SEV-SNP are enforced through a new
structure called the Reverse Map Table (RMP). The RMP is a single data
structure shared across the system that contains one entry for every 4K
page of DRAM that may be used by SEV-SNP VMs. The APM v2 section on
Secure Nested Paging (SEV-SNP) details a number of steps needed to
detect/enable SEV-SNP and RMP table support on the host:
- Detect SEV-SNP support based on CPUID bit
- Initialize the RMP table memory reported by the RMP base/end MSR
registers and configure IOMMU to be compatible with RMP access
restrictions
- Set the MtrrFixDramModEn bit in SYSCFG MSR
- Set the SecureNestedPagingEn and VMPLEn bits in the SYSCFG MSR
- Configure IOMMU
The RMP table entry format is non-architectural and it can vary by
processor. It is defined by the PPR document for each respective CPU
family. Restrict SNP support to CPU models/families which are compatible
with the current RMP table entry format to guard against any undefined
behavior when running on other system types. Future models/support will
handle this through an architectural mechanism to allow for broader
compatibility.
The SNP host code depends on CONFIG_KVM_AMD_SEV config flag which may
be enabled even when CONFIG_AMD_MEM_ENCRYPT isn't set, so update the
SNP-specific IOMMU helpers used here to rely on CONFIG_KVM_AMD_SEV
instead of CONFIG_AMD_MEM_ENCRYPT.
On Sat, Dec 30, 2023 at 10:19:32AM -0600, Michael Roth wrote:
quoted hunk
+ if (cpu_has(c, X86_FEATURE_SEV_SNP)) {+ /*+ * RMP table entry format is not architectural and it can vary by processor+ * and is defined by the per-processor PPR. Restrict SNP support on the+ * known CPU model and family for which the RMP table entry format is+ * currently defined for.+ */+ if (!(c->x86 == 0x19 && c->x86_model <= 0xaf) &&+ !(c->x86 == 0x1a && c->x86_model <= 0xf))+ setup_clear_cpu_cap(X86_FEATURE_SEV_SNP);+ else if (!snp_probe_rmptable_info())+ setup_clear_cpu_cap(X86_FEATURE_SEV_SNP);+ }
IOW, this below.
Lemme send the ZEN5 thing as a separate patch.
On Thu, Jan 04, 2024 at 12:05:27PM +0100, Jeremi Piotrowski wrote:
Is there a really good reason to perform the snp_probe_smptable_info() check at this
point (instead of in snp_rmptable_init). snp_rmptable_init will also clear the cap
on failure, and bsp_init_amd() runs too early to allow for the kernel to allocate the
rmptable itself. I pointed out in the previous review that kernel allocation of rmptable
is necessary in SNP-host capable VMs in Azure.
On Fri, Jan 05, 2024 at 05:09:16PM +0100, Borislav Petkov wrote:
On Thu, Jan 04, 2024 at 12:05:27PM +0100, Jeremi Piotrowski wrote:
quoted
Is there a really good reason to perform the snp_probe_smptable_info() check at this
point (instead of in snp_rmptable_init). snp_rmptable_init will also clear the cap
on failure, and bsp_init_amd() runs too early to allow for the kernel to allocate the
rmptable itself. I pointed out in the previous review that kernel allocation of rmptable
is necessary in SNP-host capable VMs in Azure.
What does that even mean?
That function is doing some calculations after reading two MSRs. What
can possibly go wrong?!
On Sat, Dec 30, 2023 at 10:19:32AM -0600, Michael Roth wrote:
+static int __init __snp_rmptable_init(void)
I already asked a year ago:
https://lore.kernel.org/all/Y9ubi0i4Z750gdMm@zn.tnic/
why is the __ version - __snp_rmptable_init - carved out but crickets.
It simply gets ignored. :-\
So let me do it myself, diff below.
Please add to the next version:
Co-developed-by: Borislav Petkov (AMD) <bp@alien8.de>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
after incorporating all the changes.
Thx.
---
From: Ashish Kalra <ashish.kalra@amd.com>
Pages are unsafe to be released back to the page-allocator, if they
have been transitioned to firmware/guest state and can't be reclaimed
or transitioned back to hypervisor/shared state. In this case add
them to an internal leaked pages list to ensure that they are not freed
or touched/accessed to cause fatal page faults.
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: relocate to arch/x86/virt/svm/sev.c]
Signed-off-by: Michael Roth <redacted>
Hi, sorry I didn't respond in time to the last mail discussing previous
version in
https://lore.kernel.org/all/8c1fd8da-912a-a9ce-9547-107ba8a450fc@amd.com/
due to upcoming holidays.
I would rather avoid the approach of allocating container objects:
- it's allocating memory when effectively losing memory, a dangerous thing
- are all the callers and their context ok with GFP_KERNEL?
- GFP_KERNEL_ACCOUNT seems wrong, why would we be charging this to the
current process, it's probably not its fault the pages are leaked? Also the
charging can fail?
- given the benefit of having leaked pages on a list is basically just
debugging (i.e. crash dump or drgn inspection) this seems too heavy
I think it would be better and sufficient to use page->lru for order-0 and
head pages, and simply skip tail pages (possibly with adjusted warning
message for that case).
Vlastimil
<snip>
quoted hunk
++void snp_leak_pages(u64 pfn, unsigned int npages)+{+ struct page *page = pfn_to_page(pfn);+ struct leaked_page *leak;++ pr_debug("%s: leaking PFN range 0x%llx-0x%llx\n", __func__, pfn, pfn + npages);++ spin_lock(&snp_leaked_pages_list_lock);+ while (npages--) {+ leak = kzalloc(sizeof(*leak), GFP_KERNEL_ACCOUNT);+ if (!leak)+ goto unlock;
Should we skip the dump_rmpentry() in such a case?
On Fri, Jan 05, 2024 at 05:09:16PM +0100, Borislav Petkov wrote:
quoted
On Thu, Jan 04, 2024 at 12:05:27PM +0100, Jeremi Piotrowski wrote:
quoted
Is there a really good reason to perform the snp_probe_smptable_info() check at this
point (instead of in snp_rmptable_init). snp_rmptable_init will also clear the cap
on failure, and bsp_init_amd() runs too early to allow for the kernel to allocate the
rmptable itself. I pointed out in the previous review that kernel allocation of rmptable
is necessary in SNP-host capable VMs in Azure.
What does that even mean?>>
That function is doing some calculations after reading two MSRs. What
can possibly go wrong?!
What I wrote: "allow for the kernel to allocate the rmptable". Until the kernel allocates a
rmptable the two MSRs are not initialized in a VM. This is specific to SNP-host VMs because
they don't have access to the system-wide rmptable (or a virtualized version of it), and the
rmptable is only useful for kernel internal tracking in this case. So we don't strictly need
one and could save the overhead but not having one would complicate the KVM SNP code so I'd
rather allocate one for now.
It makes most sense to perform the rmptable allocation later in kernel init, after platform
detection and e820 setup. It isn't really used until device_initcall.
https://lore.kernel.org/lkml/20230213103402.1189285-2-jpiotrowski@linux.microsoft.com/
(I'll be posting updated patches soon).
That could be one reason perhaps:
"It needs to be called early enough to allow for AutoIBRS to not be disabled
just because SNP is supported. By calling it where it is currently called, the
SNP feature can be cleared if, even though supported, SNP can't be used,
allowing AutoIBRS to be used as a more performant Spectre mitigation."
https://lore.kernel.org/r/8ec38db1-5ccf-4684-bc0d-d48579ebf0d0@amd.com
This logic seems twisted. Why use firmware rmptable allocation as a proxy for SEV-SNP
enablement if BIOS provides an explicit flag to enable/disable SEV-SNP support. That
would be a better signal to use to control AutoIBRS enablement.
On Mon, Jan 08, 2024 at 05:49:01PM +0100, Jeremi Piotrowski wrote:
What I wrote: "allow for the kernel to allocate the rmptable".
What?!
"15.36.5 Hypervisor RMP Management
...
Because the RMP is initialized by the AMD-SP to prevent direct access to
the RMP, the hypervisor must use the RMPUPDATE instruction to alter the
entries of the RMP. RMPUPDATE allows the hypervisor to alter the
Guest_Physical_Address, Assigned, Page_Size, Immutable, and ASID fields
of an RMP entry."
What you want is something that you should keep far and away from the
upstream kernel.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
On Mon, Jan 08, 2024 at 05:49:01PM +0100, Jeremi Piotrowski wrote:
quoted
What I wrote: "allow for the kernel to allocate the rmptable".
What?!
"15.36.5 Hypervisor RMP Management
...
Because the RMP is initialized by the AMD-SP to prevent direct access to
the RMP, the hypervisor must use the RMPUPDATE instruction to alter the
entries of the RMP. RMPUPDATE allows the hypervisor to alter the
Guest_Physical_Address, Assigned, Page_Size, Immutable, and ASID fields
of an RMP entry."
quoted
What you want is something that you should keep far and away from the
upstream kernel.
Can we please not assume I am acting in bad faith. I am explicitly trying to
integrate nicely with AMD's KVM SNP host patches to cover an additional usecase
and get something upstreamable.
Let's separate RMP allocation from who (and how) maintains the entries.
"""
15.36.4 Initializing the RMP
...
Software must program RMP_BASE and RMP_END identically for each core in the
system and before enabling SEV-SNP globally.
"""
KVM expects UEFI to do this, Hyper-V does the allocation itself (on bare-metal).
Both are valid. Afaik it is the SNP_INIT command that hands over control of the
RMP from software to AMD-SP.
When it comes to "who and how maintains the rmp" - that is of course the AMD-SP
and hypervisor issues RMPUPDATE instructions. The paragraph you cite talks about
the physical RMP and AMD-SP - not virtualized SNP (aka "SNP-host VM"/nested SNP).
AMD specified an MSR-based RMPUPDATE for us for that usecase (15.36.19 SEV-SNP
Instruction Virtualization). The RMP inside the SNP-host VM is not related to
the physical RMP and is an entirely software based construct.
The RMP in nested SNP is only used for kernel bookkeeping and so its allocation
is optional. KVM could do without reading the RMP directly altogether (by tracking
the assigned bit somewhere) but that would be a design change and I'd rather see
the KVM SNP host patches merged in their current shape. Which is why the patch
I linked allocates a (shadow) RMP from the kernel.
I would very much appreciate if we would not prevent that usecase from working -
that's why I've been reviewing and testing multiple revisions of these patches
and providing feedback all along.
On Tue, Jan 09, 2024 at 12:56:17PM +0100, Jeremi Piotrowski wrote:
Can we please not assume I am acting in bad faith.
No you're not acting with bad faith.
What you're doing, in my experience so far is, you come with some weird
HV + guest models which has been invented somewhere, behind some closed
doors, then you come with some desire that the upstream kernel should
support it and you're not even documenting it properly and I'm left with
asking questions all the time, what is this, what's the use case,
blabla.
Don't take this personally - I guess this is all due to NDAs,
development schedules, and whatever else and yes, I've heard it all.
But just because you want this, we're not going to jump on it and
support it unconditionally. It needs to integrate properly with the rest
of the kernel and if it doesn't, it is not going upstream. That simple.
I am explicitly trying to integrate nicely with AMD's KVM SNP host
patches to cover an additional usecase and get something upstreamable.
And yet I still have no clue what your use case is. I always have to go
ask behind the scenes and get some half-answers about *maybe* this is
what they support.
Looking at the patch you pointed at I see there a proper explanation of
your nested SNP stuff. Finally!
From now on, please make sure your use case is properly explained
before you come with patches.
The RMP in nested SNP is only used for kernel bookkeeping and so its
allocation is optional. KVM could do without reading the RMP directly
altogether (by tracking the assigned bit somewhere) but that would be
a design change and I'd rather see the KVM SNP host patches merged in
their current shape. Which is why the patch I linked allocates
a (shadow) RMP from the kernel.
At least three issues I see with that:
- the allocation can fail so it is a lot more convenient when the
firmware prepares it
- the RMP_BASE and RMP_END writes need to be verified they actially did
set up the RMP range because if they haven't, you might as well
throw SNP security out of the window. In general, letting the kernel
do the RMP allocation needs to be verified very very thoroughly.
- a future feature might make this more complicated
I would very much appreciate if we would not prevent that usecase from
working - that's why I've been reviewing and testing multiple
revisions of these patches and providing feedback all along.
On Tue, Jan 09, 2024 at 01:29:06PM +0100, Borislav Petkov wrote:
At least three issues I see with that:
- the allocation can fail so it is a lot more convenient when the
firmware prepares it
- the RMP_BASE and RMP_END writes need to be verified they actially did
set up the RMP range because if they haven't, you might as well
throw SNP security out of the window. In general, letting the kernel
do the RMP allocation needs to be verified very very thoroughly.
- a future feature might make this more complicated
- What do you do if you boot on a system which has the RMP already
allocated in the BIOS?
- How do you detect that it is the L1 kernel that must allocate the RMP?
- Why can't you use the BIOS allocated RMP in your scenario too instead
of the L1 kernel allocating it?
- ...
I might think of more.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
Hello Vlastimil,
On 1/8/2024 4:45 AM, Vlastimil Babka wrote:
On 12/30/23 17:19, Michael Roth wrote:
quoted
From: Ashish Kalra <ashish.kalra@amd.com>
Pages are unsafe to be released back to the page-allocator, if they
have been transitioned to firmware/guest state and can't be reclaimed
or transitioned back to hypervisor/shared state. In this case add
them to an internal leaked pages list to ensure that they are not freed
or touched/accessed to cause fatal page faults.
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: relocate to arch/x86/virt/svm/sev.c]
Signed-off-by: Michael Roth <redacted>
Hi, sorry I didn't respond in time to the last mail discussing previous
version in
https://lore.kernel.org/all/8c1fd8da-912a-a9ce-9547-107ba8a450fc@amd.com/
due to upcoming holidays.
I would rather avoid the approach of allocating container objects:
- it's allocating memory when effectively losing memory, a dangerous thing
- are all the callers and their context ok with GFP_KERNEL?
- GFP_KERNEL_ACCOUNT seems wrong, why would we be charging this to the
current process, it's probably not its fault the pages are leaked? Also the
charging can fail?
- given the benefit of having leaked pages on a list is basically just
debugging (i.e. crash dump or drgn inspection) this seems too heavy
I think it would be better and sufficient to use page->lru for order-0 and
head pages, and simply skip tail pages (possibly with adjusted warning
message for that case).
Vlastimil
<snip
Considering the above thoughts, this is updated version of snp_leak_pages(), looking forward to any review comments/feedback you have on the same:
void snp_leak_pages(u64 pfn, unsigned int npages)
{
struct page *page = pfn_to_page(pfn);
pr_debug("%s: leaking PFN range 0x%llx-0x%llx\n", __func__, pfn, pfn + npages);
spin_lock(&snp_leaked_pages_list_lock);
while (npages--) {
/*
* Reuse the page's buddy list for chaining into the leaked
* pages list. This page should not be on a free list currently
* and is also unsafe to be added to a free list.
*/
if ((likely(!PageCompound(page))) || (PageCompound(page) &&
!PageTail(page) && compound_head(page) == page))
/*
* Skip inserting tail pages of compound page as
* page->buddy_list of tail pages is not usable.
*/
list_add_tail(&page->buddy_list, &snp_leaked_pages_list);
sev_dump_rmpentry(pfn);
snp_nr_leaked_pages++;
pfn++;
page++;
}
spin_unlock(&snp_leaked_pages_list_lock);
}
Thanks, Ashish
Hello Vlastimil,
On 1/8/2024 4:45 AM, Vlastimil Babka wrote:
quoted
On 12/30/23 17:19, Michael Roth wrote:
quoted
From: Ashish Kalra <ashish.kalra@amd.com>
Pages are unsafe to be released back to the page-allocator, if they
have been transitioned to firmware/guest state and can't be reclaimed
or transitioned back to hypervisor/shared state. In this case add
them to an internal leaked pages list to ensure that they are not freed
or touched/accessed to cause fatal page faults.
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: relocate to arch/x86/virt/svm/sev.c]
Signed-off-by: Michael Roth <redacted>
Hi, sorry I didn't respond in time to the last mail discussing previous
version in
https://lore.kernel.org/all/8c1fd8da-912a-a9ce-9547-107ba8a450fc@amd.com/
due to upcoming holidays.
I would rather avoid the approach of allocating container objects:
- it's allocating memory when effectively losing memory, a dangerous thing
- are all the callers and their context ok with GFP_KERNEL?
- GFP_KERNEL_ACCOUNT seems wrong, why would we be charging this to the
current process, it's probably not its fault the pages are leaked? Also the
charging can fail?
- given the benefit of having leaked pages on a list is basically just
debugging (i.e. crash dump or drgn inspection) this seems too heavy
I think it would be better and sufficient to use page->lru for order-0 and
head pages, and simply skip tail pages (possibly with adjusted warning
message for that case).
Vlastimil
<snip
Considering the above thoughts, this is updated version of
snp_leak_pages(), looking forward to any review comments/feedback you
have on the same:
void snp_leak_pages(u64 pfn, unsigned int npages)
{
struct page *page = pfn_to_page(pfn);
pr_debug("%s: leaking PFN range 0x%llx-0x%llx\n", __func__,
pfn, pfn + npages);
spin_lock(&snp_leaked_pages_list_lock);
while (npages--) {
/*
* Reuse the page's buddy list for chaining into the leaked
* pages list. This page should not be on a free list
currently
* and is also unsafe to be added to a free list.
*/
if ((likely(!PageCompound(page))) || (PageCompound(page) &&
!PageTail(page) && compound_head(page) == page))
This is unnecessarily paranoid wrt that compound_head(page) test, but OTOH
doesn't handle the weird case when we're leaking less than whole compound
page (if that can even happen). So I'd suggest:
while (npages) {
if ((likely(!PageCompound(page))) || (PageHead(page) && compound_nr(page)
<= npages))
list_add_tail(&page->buddy_list, ...)
}
... (no change from yours)
npages--;
}
(or an equivalent for()) perhaps
/*
* Skip inserting tail pages of compound page as
* page->buddy_list of tail pages is not usable.
*/
list_add_tail(&page->buddy_list,
&snp_leaked_pages_list);
sev_dump_rmpentry(pfn);
snp_nr_leaked_pages++;
pfn++;
page++;
}
spin_unlock(&snp_leaked_pages_list_lock);
}
Thanks, Ashish
On Sat, Dec 30, 2023 at 10:19:35AM -0600, Michael Roth wrote:
quoted hunk
+ while (pfn_current < pfn_end) {+ e = __snp_lookup_rmpentry(pfn_current, &level);+ if (IS_ERR(e)) {+ pfn_current++;+ continue;+ }++ e_data = (u64 *)e;+ if (e_data[0] || e_data[1]) {+ pr_info("No assigned RMP entry for PFN 0x%llx, but the 2MB region contains populated RMP entries, e.g.: PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",+ pfn, pfn_current, e_data[1], e_data[0]);+ return;+ }+ pfn_current++;+ }++ pr_info("No populated RMP entries in the 2MB region containing PFN 0x%llx\n",+ pfn);+}
Ok, I went and reworked this, see below.
Yes, I think it is important - at least in the beginning - to dump the
whole 2M PFN region for debugging purposes. If that output starts
becoming too unwieldy and overflowing terminals or log files, we'd
shorten it or put it behind a debug option or so.
Thx.
---
From: Sean Christopherson <seanjc@google.com> Date: 2024-01-10 14:59:58
On Sat, Dec 30, 2023, Michael Roth wrote:
From: Brijesh Singh <redacted>
Add support to decrypt guest encrypted memory. These API interfaces can
be used for example to dump VMCBs on SNP guest exit.
By who? Nothing in this series, or the KVM series, ever invokes
snp_guest_dbg_decrypt_page().
From: Tom Lendacky <thomas.lendacky@amd.com> Date: 2024-01-10 15:10:59
On 12/30/23 10:19, Michael Roth wrote:
quoted hunk
From: Brijesh Singh <redacted>
This information will be useful for debugging things like page faults
due to RMP access violations and RMPUPDATE failures.
Signed-off-by: Brijesh Singh <redacted>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
[mdr: move helper to standalone patch, rework dump logic to reduce
verbosity]
Signed-off-by: Michael Roth <redacted>
---
arch/x86/include/asm/sev.h | 2 +
arch/x86/virt/svm/sev.c | 77 ++++++++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+)
@@ -266,3 +266,80 @@ int snp_lookup_rmpentry(u64 pfn, bool *assigned, int *level)return0;}EXPORT_SYMBOL_GPL(snp_lookup_rmpentry);++/*+*DumptherawRMPentryforaparticularPFN.Thesebitsaredocumentedinthe+*PPRforaparticularCPUmodelandprovideusefulinformationabouthowa+*particularPFNisbeingutilizedbythekernel/firmwareatthetimecertain+*unexpectedeventsoccur,suchasRMPfaults.+*/+staticvoiddump_rmpentry(u64pfn)+{+u64pfn_current,pfn_end;+structrmpentry*e;+u64*e_data;+intlevel;++e=__snp_lookup_rmpentry(pfn,&level);+if(IS_ERR(e)){+pr_info("Failed to read RMP entry for PFN 0x%llx, error %ld\n",+pfn,PTR_ERR(e));+return;+}++e_data=(u64*)e;+if(e->assigned){+pr_info("RMP entry for PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",+pfn,e_data[1],e_data[0]);+return;+}++/*+*IftheRMPentryforaparticularPFNisnotinanassignedstate,+*thenitissometimesusefultogetanideaofwhetherornotanyRMP+*entriesforotherPFNswithinthesame2MBregionareassigned,since+*thosetoocanaffecttheabilitytoaccessaparticularPFNin+*certainsituations,suchaswhenthePFNisbeingaccessedviaa2MB+*mappinginthehostpagetable.+*/+pfn_current=ALIGN(pfn,PTRS_PER_PMD);+pfn_end=pfn_current+PTRS_PER_PMD;++while(pfn_current<pfn_end){+e=__snp_lookup_rmpentry(pfn_current,&level);+if(IS_ERR(e)){+pfn_current++;+continue;+}++e_data=(u64*)e;+if(e_data[0]||e_data[1]){+pr_info("No assigned RMP entry for PFN 0x%llx, but the 2MB region contains populated RMP entries, e.g.: PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",+pfn,pfn_current,e_data[1],e_data[0]);+return;+}+pfn_current++;+}++pr_info("No populated RMP entries in the 2MB region containing PFN 0x%llx\n",+pfn);+}++voidsnp_dump_hva_rmpentry(unsignedlonghva)+{+unsignedintlevel;+pgd_t*pgd;+pte_t*pte;++pgd=__va(read_cr3_pa());+pgd+=pgd_index(hva);+pte=lookup_address_in_pgd(pgd,hva,&level);++if(!pte){+pr_info("Can't dump RMP entry for HVA %lx: no PTE/PFN found\n",hva);+return;+}++dump_rmpentry(pte_pfn(*pte));
Already worked with Mike offline when I was running into issues using this function. Net of that conversation is that the PFN needs to be adjusted using the address offset if the PTE level indicates a huge page.
Additionally the loop in dump_rmpentry() needs to use ALIGN_DOWN() in order to get the PFN of the starting 2MB area.
Thanks,
Tom
From: Tom Lendacky <thomas.lendacky@amd.com> Date: 2024-01-10 15:20:50
On 1/10/24 05:13, Borislav Petkov wrote:
quoted hunk
On Sat, Dec 30, 2023 at 10:19:35AM -0600, Michael Roth wrote:
quoted
+ while (pfn_current < pfn_end) {+ e = __snp_lookup_rmpentry(pfn_current, &level);+ if (IS_ERR(e)) {+ pfn_current++;+ continue;+ }++ e_data = (u64 *)e;+ if (e_data[0] || e_data[1]) {+ pr_info("No assigned RMP entry for PFN 0x%llx, but the 2MB region contains populated RMP entries, e.g.: PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",+ pfn, pfn_current, e_data[1], e_data[0]);+ return;+ }+ pfn_current++;+ }++ pr_info("No populated RMP entries in the 2MB region containing PFN 0x%llx\n",+ pfn);+}
Ok, I went and reworked this, see below.
Yes, I think it is important - at least in the beginning - to dump the
whole 2M PFN region for debugging purposes. If that output starts
becoming too unwieldy and overflowing terminals or log files, we'd
shorten it or put it behind a debug option or so.
Thx.
---
How about saying "... dumping all non-zero entries in the whole ..."
and then removing the print below that prints the PFN and "..."
quoted hunk
++ while (pfn_i < pfn_end) {+ e = __snp_lookup_rmpentry(pfn_i, &level); if (IS_ERR(e)) {- pfn_current++;+ pr_err("Error %ld reading RMP entry for PFN 0x%llx\n",+ PTR_ERR(e), pfn_i);+ pfn_i++; continue; }- e_data = (u64 *)e;- if (e_data[0] || e_data[1]) {- pr_info("No assigned RMP entry for PFN 0x%llx, but the 2MB region contains populated RMP entries, e.g.: PFN 0x%llx: [high=0x%016llx low=0x%016llx]\n",- pfn, pfn_current, e_data[1], e_data[0]);- return;- }- pfn_current++;- }+ if (e->lo || e->hi)+ pr_info("PFN: 0x%llx, [0x%016llx - 0x%016llx]\n", pfn_i, e->lo, e->hi);+ else+ pr_info("PFN: 0x%llx ...\n", pfn_i);
Remove this one.
That should cut down on excess output since you are really only concerned with non-zero RMP entries when the input PFN RMP entry is not assigned.
Thanks,
Tom
quoted hunk
- pr_info("No populated RMP entries in the 2MB region containing PFN 0x%llx\n",- pfn);+ pfn_i++;+ } } void snp_dump_hva_rmpentry(unsigned long hva)
@@ -339,4 +343,3 @@ void snp_dump_hva_rmpentry(unsigned long hva) dump_rmpentry(pte_pfn(*pte)); }-EXPORT_SYMBOL_GPL(snp_dump_hva_rmpentry);
From: Tom Lendacky <thomas.lendacky@amd.com> Date: 2024-01-10 15:51:10
On 1/10/24 09:27, Borislav Petkov wrote:
On Wed, Jan 10, 2024 at 09:20:44AM -0600, Tom Lendacky wrote:
quoted
How about saying "... dumping all non-zero entries in the whole ..."
I'm trying not to have long stories in printk statements :)
Well it only adds "non-zero"
quoted
and then removing the print below that prints the PFN and "..."
Why remove the print? You want to print every non-null RMP entry in the
2M range, no?
I'm only suggesting getting rid of the else that prints "..." when the entry is all zeroes. Printing the non-zero entries would still occur.
Thanks,
Tom
On Wed, Jan 10, 2024 at 10:18:37PM +0200, Jarkko Sakkinen wrote:
quoted
quoted
+ if (!pte) {
+ pr_info("Can't dump RMP entry for HVA %lx: no PTE/PFN found\n", hva);
~~~~~~~
is this correct log level?
No, and I caught a couple of those already but missed this one, thanks.
Mike, please make sure all your error prints are pr_err.
Thx.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
From: Michael Roth <hidden> Date: 2024-01-11 00:53:34
On Wed, Jan 10, 2024 at 06:59:56AM -0800, Sean Christopherson wrote:
On Sat, Dec 30, 2023, Michael Roth wrote:
quoted
From: Brijesh Singh <redacted>
Add support to decrypt guest encrypted memory. These API interfaces can
be used for example to dump VMCBs on SNP guest exit.
By who? Nothing in this series, or the KVM series, ever invokes
snp_guest_dbg_decrypt_page().
There's a VMSA dump patch we've been using internally, but yah, this should
be dropped from the series until that patch is actually submitted upstream.
-Mike
On Sat, Dec 30, 2023 at 10:19:38AM -0600, Michael Roth wrote:
quoted hunk
+static int rmpupdate(u64 pfn, struct rmp_state *state)+{+ unsigned long paddr = pfn << PAGE_SHIFT;+ int ret;++ if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))+ return -ENODEV;+
Let's document this deal here and leave the door open for potential
future improvements:
---
On Sat, Dec 30, 2023 at 10:19:39AM -0600, Michael Roth wrote:
quoted hunk
static int rmpupdate(u64 pfn, struct rmp_state *state) { unsigned long paddr = pfn << PAGE_SHIFT;- int ret;+ int ret, level, npages; if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP)) return -ENODEV;+ level = RMP_TO_PG_LEVEL(state->pagesize);+ npages = page_level_size(level) / PAGE_SIZE;++ /*+ * If the kernel uses a 2MB directmap mapping to write to an address,+ * and that 2MB range happens to contain a 4KB page that set to private+ * in the RMP table, an RMP #PF will trigger and cause a host crash.+ *+ * Prevent this by removing pages from the directmap prior to setting+ * them as private in the RMP table.+ */+ if (state->assigned && invalidate_direct_map(pfn, npages))+ return -EFAULT;
Well, the comment says one thing but the code does not do quite what the
text says:
* where is the check that pfn is part of the kernel direct map?
* where is the check that this address is part of a 2M PMD in the direct
map so that the situation is even given that you can have a 4K private
PTE there?
What this does is simply invalidate @npages unconditionally.
Then, __set_pages_np() already takes a number of pages and a start
address. Why is this thing iterating instead of sending *all* npages in
one go?
Yes, you'd need two new helpers, something like:
set_pages_present(struct page *page, int numpages)
set_pages_non_present(struct page *page, int numpages)
which call the lowlevel counterparts.
For some reason I remember asking this a while ago...
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
From: Dave Hansen <hidden> Date: 2024-01-12 20:00:06
On 12/30/23 08:19, Michael Roth wrote:
If the kernel uses a 2MB directmap mapping to write to an address, and
that 2MB range happens to contain a 4KB page that set to private in the
RMP table, that will also lead to a page-fault exception.
I thought we agreed long ago to just demote the whole direct map to 4k
on kernels that might need to act as SEV-SNP hosts. That should be step
one and this can be discussed as an optimization later.
On Fri, Jan 12, 2024 at 12:00:01PM -0800, Dave Hansen wrote:
On 12/30/23 08:19, Michael Roth wrote:
quoted
If the kernel uses a 2MB directmap mapping to write to an address, and
that 2MB range happens to contain a 4KB page that set to private in the
RMP table, that will also lead to a page-fault exception.
I thought we agreed long ago to just demote the whole direct map to 4k
on kernels that might need to act as SEV-SNP hosts. That should be step
one and this can be discussed as an optimization later.
On Fri, Jan 12, 2024 at 12:00:01PM -0800, Dave Hansen wrote:
quoted
On 12/30/23 08:19, Michael Roth wrote:
quoted
If the kernel uses a 2MB directmap mapping to write to an address, and
that 2MB range happens to contain a 4KB page that set to private in the
RMP table, that will also lead to a page-fault exception.
I thought we agreed long ago to just demote the whole direct map to 4k
on kernels that might need to act as SEV-SNP hosts. That should be step
one and this can be discussed as an optimization later.
What would be the disadvantage here? Higher TLB pressure when running
kernel code I guess...
From: Tom Lendacky <thomas.lendacky@amd.com> Date: 2024-01-12 20:28:20
On 1/12/24 14:07, Borislav Petkov wrote:
On Fri, Jan 12, 2024 at 12:00:01PM -0800, Dave Hansen wrote:
quoted
On 12/30/23 08:19, Michael Roth wrote:
quoted
If the kernel uses a 2MB directmap mapping to write to an address, and
that 2MB range happens to contain a 4KB page that set to private in the
RMP table, that will also lead to a page-fault exception.
I thought there was also a desire to remove the direct map for any pages assigned to a guest as private, not just the case that the comment says. So updating the comment would probably the best action.
quoted
I thought we agreed long ago to just demote the whole direct map to 4k
on kernels that might need to act as SEV-SNP hosts. That should be step
one and this can be discussed as an optimization later.
Won't this accomplish that without actually demoting a lot of long-live kernel related mappings that would never be demoted? I don't think we need to demote the whole mapping to 4K.
Thanks,
Tom
What would be the disadvantage here? Higher TLB pressure when running
kernel code I guess...
From: Dave Hansen <hidden> Date: 2024-01-12 20:37:35
On 1/12/24 12:28, Tom Lendacky wrote:
I thought there was also a desire to remove the direct map for any pages
assigned to a guest as private, not just the case that the comment says.
So updating the comment would probably the best action.
I'm not sure who desires that.
It's sloooooooow to remove things from the direct map. There's almost
certainly a frequency cutoff where running the whole direct mapping as
4k is better than the cost of mapping/unmapping.
Actually, where _is_ the TLB flushing here?
On Sat, Dec 30, 2023 at 10:19:39AM -0600, Michael Roth wrote:
quoted hunk
+ /*+ * If the kernel uses a 2MB directmap mapping to write to an address,+ * and that 2MB range happens to contain a 4KB page that set to private+ * in the RMP table, an RMP #PF will trigger and cause a host crash.
How nice, thanks for that!
Do you have some refs to Mike's tests so that we could run them here too
with SNP guests to see how big - if any - the fragmentation has.
Thx.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
On Fri, Jan 12, 2024 at 12:00:01PM -0800, Dave Hansen wrote:
I thought we agreed long ago to just demote the whole direct map to 4k
on kernels that might need to act as SEV-SNP hosts. That should be step
one and this can be discussed as an optimization later.
How nice, thanks for that!
Do you have some refs to Mike's tests so that we could run them here too
with SNP guests to see how big - if any - the fragmentation has.
How nice, thanks for that!
Do you have some refs to Mike's tests so that we could run them here too
with SNP guests to see how big - if any - the fragmentation has.
I thought there was also a desire to remove the direct map for any pages
assigned to a guest as private, not just the case that the comment says.
So updating the comment would probably the best action.
I'm not sure who desires that.
It's sloooooooow to remove things from the direct map. There's almost
certainly a frequency cutoff where running the whole direct mapping as
4k is better than the cost of mapping/unmapping.
Actually, where _is_ the TLB flushing here?
Hm yeah it seems to be using the _noflush version? Maybe the RMP issues this
avoids are only triggered with actual page tables and a stray outdated TLB
hit doesn't trigger it? Needs documenting though if that's the case.
On Sat, Dec 30, 2023 at 10:19:40AM -0600, Michael Roth wrote:
From: Brijesh Singh <redacted>
AMD introduced the next generation of SEV called SEV-SNP (Secure Nested
Paging). SEV-SNP builds upon existing SEV and SEV-ES functionality
while adding new hardware security protection.
Define the commands and structures used to communicate with the AMD-SP
when creating and managing the SEV-SNP guests. The SEV-SNP firmware spec
is available at developer.amd.com/sev.
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Brijesh Singh <redacted>
[mdr: update SNP command list and SNP status struct based on current
spec, use C99 flexible arrays, fix kernel-doc issues]
Signed-off-by: Michael Roth <redacted>
---
drivers/crypto/ccp/sev-dev.c | 16 +++
include/linux/psp-sev.h | 264 +++++++++++++++++++++++++++++++++++
include/uapi/linux/psp-sev.h | 56 ++++++++
3 files changed, 336 insertions(+)
On Sat, Dec 30, 2023 at 10:19:41AM -0600, Michael Roth wrote:
quoted hunk
+ /*+ * The following sequence must be issued before launching the+ * first SNP guest to ensure all dirty cache lines are flushed,+ * including from updates to the RMP table itself via RMPUPDATE+ * instructions:+ *+ * - WBINDV on all running CPUs+ * - SEV_CMD_SNP_INIT[_EX] firmware command+ * - WBINDV on all running CPUs+ * - SEV_CMD_SNP_DF_FLUSH firmware command+ */
On Sat, Dec 30, 2023 at 10:19:41AM -0600, Michael Roth wrote:
From: Brijesh Singh <redacted>
Before SNP VMs can be launched, the platform must be appropriately
configured and initialized. Platform initialization is accomplished via
the SNP_INIT command. Make sure to do a WBINVD and issue DF_FLUSH
command to prepare for the first SNP guest launch after INIT.
^^^^^^
Which "INIT"?
Sounds like after hipervisor's init...
During the execution of SNP_INIT command, the firmware configures
and enables SNP security policy enforcement in many system components.
Some system components write to regions of memory reserved by early
x86 firmware (e.g. UEFI). Other system components write to regions
provided by the operation system, hypervisor, or x86 firmware.
Such system components can only write to HV-fixed pages or Default
pages. They will error when attempting to write to other page states
"... to pages in other page states... "
after SNP_INIT enables their SNP enforcement.
And yes, this version looks much better. Some text cleanups ontop:
---
@@ -549,24 +549,22 @@ static int __sev_snp_init_locked(int *error)return0;}-/*-*TheSNP_INITrequirestheMSR_VM_HSAVE_PAmustbesetto0h-*acrossallcores.-*/+/* SNP_INIT requires MSR_VM_HSAVE_PA to be cleared on all CPUs. */on_each_cpu(snp_set_hsave_pa,NULL,1);/*-*StartinginSNPfirmwarev1.52,theSNP_INIT_EXcommandtakesalistof-*systemphysicaladdressrangestoconvertintotheHV-fixedpagestates-*duringtheRMPinitialization.Forinstance,thememorythatUEFI-*reservesshouldbeincludedintherangelist.Thisallowssystem+*StartinginSNPfirmwarev1.52,theSNP_INIT_EXcommandtakesalist+*ofsystemphysicaladdressrangestoconvertintoHV-fixedpage+*statesduringtheRMPinitialization.Forinstance,thememorythat+*UEFIreservesshouldbeincludedinthethatlist.Thisallowssystem*componentsthatoccasionallywritetomemory(e.g.loggingtoUEFI-*reservedregions)tonotfailduetoRMPinitializationandSNPenablement.+*reservedregions)tonotfailduetoRMPinitializationandSNP+*enablement.*/if(sev_version_greater_or_equal(SNP_MIN_API_MAJOR,52)){/**Firmwarechecksthatthepagescontainingtherangesenumerated-*intheRANGESstructureareeitherintheDefaultpagestateorinthe+*intheRANGESstructureareeitherinthedefaultpagestateorinthe*firmwarepagestate.*/snp_range_list=kzalloc(PAGE_SIZE,GFP_KERNEL);
@@ -577,7 +575,7 @@ static int __sev_snp_init_locked(int *error)}/*-*RetrieveallreservedmemoryregionssetupbyUEFIfromthee820memorymap+*Retrieveallreservedmemoryregionsfromthee820memorymap*tobesetupasHV-fixedpages.*/rc=walk_iomem_res_desc(IORES_DESC_NONE,IORESOURCE_MEM,0,~0,
@@ -599,14 +597,13 @@ static int __sev_snp_init_locked(int *error)}/*-*Thefollowingsequencemustbeissuedbeforelaunchingthe-*firstSNPguesttoensurealldirtycachelinesareflushed,-*includingfromupdatestotheRMPtableitselfviaRMPUPDATE-*instructions:+*ThefollowingsequencemustbeissuedbeforelaunchingthefirstSNP+*guesttoensurealldirtycachelinesareflushed,includingfrom+*updatestotheRMPtableitselfviatheRMPUPDATEinstruction:*-*-WBINDVonallrunningCPUs+*-WBINVDonallrunningCPUs*-SEV_CMD_SNP_INIT[_EX]firmwarecommand-*-WBINDVonallrunningCPUs+*-WBINVDonallrunningCPUs*-SEV_CMD_SNP_DF_FLUSHfirmwarecommand*/wbinvd_on_all_cpus();
From: Michael Roth <hidden> Date: 2024-01-16 16:20:11
On Fri, Jan 12, 2024 at 12:37:31PM -0800, Dave Hansen wrote:
On 1/12/24 12:28, Tom Lendacky wrote:
quoted
I thought there was also a desire to remove the direct map for any pages
assigned to a guest as private, not just the case that the comment says.
So updating the comment would probably the best action.
I'm not sure who desires that.
It's sloooooooow to remove things from the direct map. There's almost
certainly a frequency cutoff where running the whole direct mapping as
4k is better than the cost of mapping/unmapping.
One area we'd been looking to optimize[1] is lots of vCPUs/guests
faulting in private memory on-demand via kernels with lazy acceptance
support. The lazy acceptance / #NPF path for each 4K/2M guest page
involves updating the corresponding RMP table entry to set it to
Guest-owned state, and as part of that we remove the PFNs from the
directmap. There is indeed potential for scalability issues due to how
directmap updates are currently handled, since they involve holding a
global cpa_lock for every update.
At the time, there were investigations on how to remove the cpa_lock,
and there's an RFC patch[2] that implements this change, but I don't
know where this stands today.
There was also some work[3] on being able to restore 2MB entries in the
directmap (currently entries are only re-added as 4K) the Mike Rapoport
pointed out to me a while back. With that, since the bulk of private
guest pages 2MB, we'd be able to avoid splitting the directmap to 4K over
time.
There was also some indication[4] that UPM/guest_memfd would eventually
manage directmap invalidations internally, so it made sense to have
SNP continue to handle this up until the point that mangement was
moved to gmem.
Those 3 things, paired with a platform-independent way of catching
unexpected kernel accesses to private guest memory, are what I think
nudged us all toward the current implementation.
But AFAIK none of these 3 things are being actively upstreamed today,
so it makes sense to re-consider how we handle the directmap in the
interim.
I did some performance tests which do seem to indicate that
pre-splitting the directmap to 4K can be substantially improve certain
SNP guest workloads. This test involves running a single 1TB SNP guest
with 128 vCPUs running "stress --vm 128 --vm-bytes 5G --vm-keep" to
rapidly fault in all of its memory via lazy acceptance, and then
measuring the rate that gmem pages are being allocated on the host by
monitoring "FileHugePages" from /proc/meminfo to get some rough gauge
of how quickly a guest can fault in it's initial working set prior to
reaching steady state. The data is a bit noisy but seems to indicate
significant improvement by taking the directmap updates out of the
lazy acceptance path, and I would only expect that to become more
significant as you scale up the number of guests / vCPUs.
# Average fault-in rate across 3 runs, measured in GB/s
unpinned | pinned to NUMA node 0
DirectMap4K 12.9 | 12.1
stddev 2.2 | 1.3
DirectMap2M+split 8.0 | 8.9
stddev 1.3 | 0.8
The downside of course is potential impact for non-SNP workloads
resulting from splitting the directmap. Mike Rapoport's numbers make
me feel a little better about it, but I don't think they apply directly
to the notion of splitting the entire directmap. It's Even he LWN article
summarizes:
"The conclusion from all of this, Rapoport continued, was that
direct-map fragmentation just does not matter — for data access, at
least. Using huge-page mappings does still appear to make a difference
for memory containing the kernel code, so allocator changes should
focus on code allocations — improving the layout of allocations for
loadable modules, for example, or allowing vmalloc() to allocate huge
pages for code. But, for kernel-data allocations, direct-map
fragmentation simply appears to not be worth worrying about."
So at the very least, if we went down this path, we would be worth
investigating the following areas in addition to general perf testing:
1) Only splitting directmap regions corresponding to kernel-allocatable
*data* (hopefully that's even feasible...)
2) Potentially deferring the split until an SNP guest is actually
run, so there isn't any impact just from having SNP enabled (though
you still take a hit from RMP checks in that case so maybe it's not
worthwhile, but that itself has been noted as a concern for users
so it would be nice to not make things even worse).
[1] https://lore.kernel.org/linux-mm/20231103000105.m3z4eijcxlxciyzd@amd.com/
[2] https://lore.kernel.org/lkml/Y7f9ZuPcIMk37KnN@gmail.com/T/#m15b74841f5319c0d1177f118470e9714d4ea96c8
[3] https://lore.kernel.org/linux-kernel/20200416213229.19174-1-kirill.shutemov@linux.intel.com/
[4] https://lore.kernel.org/all/YyGLXXkFCmxBfu5U@google.com/
Actually, where _is_ the TLB flushing here?
Boris pointed that out in v6, and we implemented it in v7, but it
completely cratered performance:
https://lore.kernel.org/linux-mm/20221219150026.bltiyk72pmdc2ic3@amd.com/
After further discussion I think we'd concluded it wasn't necessary. Maybe
that's worth revisiting though. If it is necessary, then that would be
another reason to just pre-split the directmap because the above-mentioned
lazy acceptance workload/bottleneck would likely get substantially worse.
-Mike