diff mbox series

[Part2,RFC,v4,05/40] x86/sev: Add RMP entry lookup helpers

Message ID 20210707183616.5620-6-brijesh.singh@amd.com (mailing list archive)
State New
Headers show
Series Add AMD Secure Nested Paging (SEV-SNP) Hypervisor Support | expand

Commit Message

Brijesh Singh July 7, 2021, 6:35 p.m. UTC
The snp_lookup_page_in_rmptable() can be used by the host to read the RMP
entry for a given page. The RMP entry format is documented in AMD PPR, see
https://bugzilla.kernel.org/attachment.cgi?id=296015.

Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
 arch/x86/include/asm/sev.h |  4 +--
 arch/x86/kernel/sev.c      | 26 +++++++++++++++++++
 include/linux/sev.h        | 51 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 78 insertions(+), 3 deletions(-)
 create mode 100644 include/linux/sev.h

Comments

Sean Christopherson July 15, 2021, 6:37 p.m. UTC | #1
On Wed, Jul 07, 2021, Brijesh Singh wrote:
> The snp_lookup_page_in_rmptable() can be used by the host to read the RMP
> entry for a given page. The RMP entry format is documented in AMD PPR, see
> https://bugzilla.kernel.org/attachment.cgi?id=296015.

Ewwwwww, the RMP format isn't architectural!?

  Architecturally the format of RMP entries are not specified in APM. In order
  to assist software, the following table specifies select portions of the RMP
  entry format for this specific product.

I know we generally don't want to add infrastructure without good reason, but on
the other hand exposing a microarchitectural data structure to the kernel at large
is going to be a disaster if the format does change on a future processor.

Looking at the future patches, dump_rmpentry() is the only power user, e.g.
everything else mostly looks at "assigned" and "level" (and one ratelimited warn
on "validated" in snp_make_page_shared(), but I suspect that particular check
can and should be dropped).

So, what about hiding "struct rmpentry" and possibly renaming it to something
scary/microarchitectural, e.g. something like

/*
 * Returns 1 if the RMP entry is assigned, 0 if it exists but is not assigned,
 * and -errno if there is no corresponding RMP entry.
 */
int snp_lookup_rmpentry(struct page *page, int *level)
{
	unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
	struct rmpentry *entry, *large_entry;
	unsigned long vaddr;

	if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
		return -ENXIO;

	vaddr = rmptable_start + rmptable_page_offset(phys);
	if (unlikely(vaddr > rmptable_end))
		return -EXNIO;

	entry = (struct rmpentry *)vaddr;

	/* Read a large RMP entry to get the correct page level used in RMP entry. */
	vaddr = rmptable_start + rmptable_page_offset(phys & PMD_MASK);
	large_entry = (struct rmpentry *)vaddr;
	*level = RMP_TO_X86_PG_LEVEL(rmpentry_pagesize(large_entry));

	return !!entry->assigned;
}


And then move dump_rmpentry() (or add a helper) in sev.c so that "struct rmpentry"
can be declared in sev.c.

> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> ---
>  arch/x86/include/asm/sev.h |  4 +--
>  arch/x86/kernel/sev.c      | 26 +++++++++++++++++++
>  include/linux/sev.h        | 51 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 78 insertions(+), 3 deletions(-)
>  create mode 100644 include/linux/sev.h
> 
> diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
> index 6c23e694a109..9e7e7e737f55 100644
> --- a/arch/x86/include/asm/sev.h
> +++ b/arch/x86/include/asm/sev.h
> @@ -9,6 +9,7 @@
>  #define __ASM_ENCRYPTED_STATE_H
>  
>  #include <linux/types.h>
> +#include <linux/sev.h>

Why move things to linux/sev.h?  AFAICT, even at the end of the series, the only
users of anything in this file all reside somewhere in arch/x86.

>  #include <asm/insn.h>
>  #include <asm/sev-common.h>
>  #include <asm/bootparam.h>
> @@ -75,9 +76,6 @@ extern bool handle_vc_boot_ghcb(struct pt_regs *regs);
>  /* Software defined (when rFlags.CF = 1) */
>  #define PVALIDATE_FAIL_NOUPDATE		255
>  
> -/* RMP page size */
> -#define RMP_PG_SIZE_4K			0
> -
>  #define RMPADJUST_VMSA_PAGE_BIT		BIT(16)
>  
>  #ifdef CONFIG_AMD_MEM_ENCRYPT
> diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
> index f9d813d498fa..1aed3d53f59f 100644
> --- a/arch/x86/kernel/sev.c
> +++ b/arch/x86/kernel/sev.c
> @@ -49,6 +49,8 @@
>  #define DR7_RESET_VALUE        0x400
>  
>  #define RMPTABLE_ENTRIES_OFFSET        0x4000
> +#define RMPENTRY_SHIFT			8
> +#define rmptable_page_offset(x)	(RMPTABLE_ENTRIES_OFFSET + (((unsigned long)x) >> RMPENTRY_SHIFT))
>  
>  /* For early boot hypervisor communication in SEV-ES enabled guests */
>  static struct ghcb boot_ghcb_page __bss_decrypted __aligned(PAGE_SIZE);
> @@ -2319,3 +2321,27 @@ static int __init snp_rmptable_init(void)
>   * passthough state, and it is available after subsys_initcall().
>   */
>  fs_initcall(snp_rmptable_init);
> +
> +struct rmpentry *snp_lookup_page_in_rmptable(struct page *page, int *level)

Maybe just snp_get_rmpentry?  Or snp_lookup_rmpentry?  I'm guessing the name was
chosen to align with e.g. lookup_address_in_mm, but IMO the lookup_address helpers
are oddly named.

> +{
> +	unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
> +	struct rmpentry *entry, *large_entry;
> +	unsigned long vaddr;
> +
> +	if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
> +		return NULL;
> +
> +	vaddr = rmptable_start + rmptable_page_offset(phys);
> +	if (unlikely(vaddr > rmptable_end))
> +		return NULL;
> +
> +	entry = (struct rmpentry *)vaddr;
> +
> +	/* Read a large RMP entry to get the correct page level used in RMP entry. */
> +	vaddr = rmptable_start + rmptable_page_offset(phys & PMD_MASK);
> +	large_entry = (struct rmpentry *)vaddr;
> +	*level = RMP_TO_X86_PG_LEVEL(rmpentry_pagesize(large_entry));
> +
> +	return entry;
> +}
Brijesh Singh July 15, 2021, 7:28 p.m. UTC | #2
On 7/15/21 1:37 PM, Sean Christopherson wrote:
> On Wed, Jul 07, 2021, Brijesh Singh wrote:
>> The snp_lookup_page_in_rmptable() can be used by the host to read the RMP
>> entry for a given page. The RMP entry format is documented in AMD PPR, see
>> https://nam11.safelinks.protection.outlook.com/?url=https%3A%2F%2Fbugzilla.kernel.org%2Fattachment.cgi%3Fid%3D296015&amp;data=04%7C01%7Cbrijesh.singh%40amd.com%7C2140214b3fbd4a71617008d947bf9ae7%7C3dd8961fe4884e608e11a82d994e183d%7C0%7C0%7C637619710568694335%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=AkCyolw0P%2BrRFF%2FAnRozld4GkegQ0hR%2F523DI48jB4g%3D&amp;reserved=0.
> 
> Ewwwwww, the RMP format isn't architectural!?
> 
>    Architecturally the format of RMP entries are not specified in APM. In order
>    to assist software, the following table specifies select portions of the RMP
>    entry format for this specific product.
> 

Unfortunately yes.

But the documented fields in the RMP entry is architectural. The entry 
fields are documented in the APM section 15.36. So, in future we are 
guaranteed to have those fields available. If we are reading the RMP 
table directly, then architecture should provide some other means to get 
to fields from the RMP entry.


> I know we generally don't want to add infrastructure without good reason, but on
> the other hand exposing a microarchitectural data structure to the kernel at large
> is going to be a disaster if the format does change on a future processor.
> 
> Looking at the future patches, dump_rmpentry() is the only power user, e.g.
> everything else mostly looks at "assigned" and "level" (and one ratelimited warn
> on "validated" in snp_make_page_shared(), but I suspect that particular check
> can and should be dropped).
> 

Yes, we need "assigned" and "level" and other entries are mainly for the 
debug purposes.

> So, what about hiding "struct rmpentry" and possibly renaming it to something
> scary/microarchitectural, e.g. something like
> 

Yes, it will work fine.

> /*
>   * Returns 1 if the RMP entry is assigned, 0 if it exists but is not assigned,
>   * and -errno if there is no corresponding RMP entry.
>   */
> int snp_lookup_rmpentry(struct page *page, int *level)
> {
> 	unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
> 	struct rmpentry *entry, *large_entry;
> 	unsigned long vaddr;
> 
> 	if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
> 		return -ENXIO;
> 
> 	vaddr = rmptable_start + rmptable_page_offset(phys);
> 	if (unlikely(vaddr > rmptable_end))
> 		return -EXNIO;
> 
> 	entry = (struct rmpentry *)vaddr;
> 
> 	/* Read a large RMP entry to get the correct page level used in RMP entry. */
> 	vaddr = rmptable_start + rmptable_page_offset(phys & PMD_MASK);
> 	large_entry = (struct rmpentry *)vaddr;
> 	*level = RMP_TO_X86_PG_LEVEL(rmpentry_pagesize(large_entry));
> 
> 	return !!entry->assigned;
> }
> 
> 
> And then move dump_rmpentry() (or add a helper) in sev.c so that "struct rmpentry"
> can be declared in sev.c.
> 

Ack.


>> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
>> ---
>>   arch/x86/include/asm/sev.h |  4 +--
>>   arch/x86/kernel/sev.c      | 26 +++++++++++++++++++
>>   include/linux/sev.h        | 51 ++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 78 insertions(+), 3 deletions(-)
>>   create mode 100644 include/linux/sev.h
>>
>> diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
>> index 6c23e694a109..9e7e7e737f55 100644
>> --- a/arch/x86/include/asm/sev.h
>> +++ b/arch/x86/include/asm/sev.h
>> @@ -9,6 +9,7 @@
>>   #define __ASM_ENCRYPTED_STATE_H
>>   
>>   #include <linux/types.h>
>> +#include <linux/sev.h>
> 
> Why move things to linux/sev.h?  AFAICT, even at the end of the series, the only
> users of anything in this file all reside somewhere in arch/x86.
> 


If we go with approach where the 'struct rmpentry' is not visible 
outside the arch/x86/kernel/sev.c then there is no need to define all 
these bit fields in linux/sev.h. I kept in linux/sev.h because driver 
(KVM, and PSP) uses the rmpentry_xxx() to read the fields.


>>   #include <asm/insn.h>
>>   #include <asm/sev-common.h>
>>   #include <asm/bootparam.h>
>> @@ -75,9 +76,6 @@ extern bool handle_vc_boot_ghcb(struct pt_regs *regs);
>>   /* Software defined (when rFlags.CF = 1) */
>>   #define PVALIDATE_FAIL_NOUPDATE		255
>>   
>> -/* RMP page size */
>> -#define RMP_PG_SIZE_4K			0
>> -
>>   #define RMPADJUST_VMSA_PAGE_BIT		BIT(16)
>>   
>>   #ifdef CONFIG_AMD_MEM_ENCRYPT
>> diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
>> index f9d813d498fa..1aed3d53f59f 100644
>> --- a/arch/x86/kernel/sev.c
>> +++ b/arch/x86/kernel/sev.c
>> @@ -49,6 +49,8 @@
>>   #define DR7_RESET_VALUE        0x400
>>   
>>   #define RMPTABLE_ENTRIES_OFFSET        0x4000
>> +#define RMPENTRY_SHIFT			8
>> +#define rmptable_page_offset(x)	(RMPTABLE_ENTRIES_OFFSET + (((unsigned long)x) >> RMPENTRY_SHIFT))
>>   
>>   /* For early boot hypervisor communication in SEV-ES enabled guests */
>>   static struct ghcb boot_ghcb_page __bss_decrypted __aligned(PAGE_SIZE);
>> @@ -2319,3 +2321,27 @@ static int __init snp_rmptable_init(void)
>>    * passthough state, and it is available after subsys_initcall().
>>    */
>>   fs_initcall(snp_rmptable_init);
>> +
>> +struct rmpentry *snp_lookup_page_in_rmptable(struct page *page, int *level)
> 
> Maybe just snp_get_rmpentry?  Or snp_lookup_rmpentry?  I'm guessing the name was
> chosen to align with e.g. lookup_address_in_mm, but IMO the lookup_address helpers
> are oddly named.
> 

Yes, it was mostly choose to align with it. Dave recommended dropping 
the 'struct page *' arg from it and accept the pfn directly. Based on 
your feedbacks, I am going to add

int snp_lookup_rmpentry(unsigned long pfn, int *level);

thanks
Brijesh Singh July 16, 2021, 5:22 p.m. UTC | #3
On 7/15/21 2:28 PM, Brijesh Singh wrote:
>
>
> On 7/15/21 1:37 PM, Sean Christopherson wrote:
>> On Wed, Jul 07, 2021, Brijesh Singh wrote:
>>> The snp_lookup_page_in_rmptable() can be used by the host to read
>>> the RMP
>>> entry for a given page. The RMP entry format is documented in AMD
>>> PPR, see
>>> https://nam11.safelinks.protection.outlook.com/?url=https%3A%2F%2Fbugzilla.kernel.org%2Fattachment.cgi%3Fid%3D296015&amp;data=04%7C01%7Cbrijesh.singh%40amd.com%7C2140214b3fbd4a71617008d947bf9ae7%7C3dd8961fe4884e608e11a82d994e183d%7C0%7C0%7C637619710568694335%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=AkCyolw0P%2BrRFF%2FAnRozld4GkegQ0hR%2F523DI48jB4g%3D&amp;reserved=0.
>>>
>>
>> Ewwwwww, the RMP format isn't architectural!?
>>
>>    Architecturally the format of RMP entries are not specified in
>> APM. In order
>>    to assist software, the following table specifies select portions
>> of the RMP
>>    entry format for this specific product.
>>
>
> Unfortunately yes.
>
> But the documented fields in the RMP entry is architectural. The entry
> fields are documented in the APM section 15.36. So, in future we are
> guaranteed to have those fields available. If we are reading the RMP
> table directly, then architecture should provide some other means to
> get to fields from the RMP entry.
>
>
>> I know we generally don't want to add infrastructure without good
>> reason, but on
>> the other hand exposing a microarchitectural data structure to the
>> kernel at large
>> is going to be a disaster if the format does change on a future
>> processor.
>>
>> Looking at the future patches, dump_rmpentry() is the only power
>> user, e.g.
>> everything else mostly looks at "assigned" and "level" (and one
>> ratelimited warn
>> on "validated" in snp_make_page_shared(), but I suspect that
>> particular check
>> can and should be dropped).
>>
>
> Yes, we need "assigned" and "level" and other entries are mainly for
> the debug purposes.
>
For the debug purposes, we would like to dump additional RMP entries. If
we go with your proposed function then how do we get those information
in the dump_rmpentry()? How about if we provide two functions; the first
function provides architectural format and second provides the raw
values which can be used by the dump_rmpentry() helper.

struct rmpentry *snp_lookup_rmpentry(unsigned long paddr, int *level);

The 'struct rmpentry' uses the format defined in APM Table 15-36.

struct _rmpentry *_snp_lookup_rmpentry(unsigned long paddr, int *level);

The 'struct _rmpentry' will use include the PPR definition (basically
what we have today in this patch).

Thoughts ?


>> So, what about hiding "struct rmpentry" and possibly renaming it to
>> something
>> scary/microarchitectural, e.g. something like
>>
>
> Yes, it will work fine.
>
>> /*
>>   * Returns 1 if the RMP entry is assigned, 0 if it exists but is not
>> assigned,
>>   * and -errno if there is no corresponding RMP entry.
>>   */
>> int snp_lookup_rmpentry(struct page *page, int *level)
>> {
>>     unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
>>     struct rmpentry *entry, *large_entry;
>>     unsigned long vaddr;
>>
>>     if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
>>         return -ENXIO;
>>
>>     vaddr = rmptable_start + rmptable_page_offset(phys);
>>     if (unlikely(vaddr > rmptable_end))
>>         return -EXNIO;
>>
>>     entry = (struct rmpentry *)vaddr;
>>
>>     /* Read a large RMP entry to get the correct page level used in
>> RMP entry. */
>>     vaddr = rmptable_start + rmptable_page_offset(phys & PMD_MASK);
>>     large_entry = (struct rmpentry *)vaddr;
>>     *level = RMP_TO_X86_PG_LEVEL(rmpentry_pagesize(large_entry));
>>
>>     return !!entry->assigned;
>> }
>>
>>
>> And then move dump_rmpentry() (or add a helper) in sev.c so that
>> "struct rmpentry"
>> can be declared in sev.c.
>>
>
Sean Christopherson July 20, 2021, 10:06 p.m. UTC | #4
On Fri, Jul 16, 2021, Brijesh Singh wrote:
> 
> On 7/15/21 2:28 PM, Brijesh Singh wrote:
> >> Looking at the future patches, dump_rmpentry() is the only power user,
> >> e.g.  everything else mostly looks at "assigned" and "level" (and one
> >> ratelimited warn on "validated" in snp_make_page_shared(), but I suspect
> >> that particular check can and should be dropped).
> >
> > Yes, we need "assigned" and "level" and other entries are mainly for
> > the debug purposes.
> >
> For the debug purposes, we would like to dump additional RMP entries. If
> we go with your proposed function then how do we get those information
> in the dump_rmpentry()?

As suggested below, move dump_rmpentry() into sev.c so that it can use the
microarchitectural version.  For debug, I'm pretty that's what we'll want anyways,
e.g. dump the raw value along with the meaning of various bits.

> How about if we provide two functions; the first
> function provides architectural format and second provides the raw
> values which can be used by the dump_rmpentry() helper.
> 
> struct rmpentry *snp_lookup_rmpentry(unsigned long paddr, int *level);
> 
> The 'struct rmpentry' uses the format defined in APM Table 15-36.
> 
> struct _rmpentry *_snp_lookup_rmpentry(unsigned long paddr, int *level);
> 
> The 'struct _rmpentry' will use include the PPR definition (basically
> what we have today in this patch).
> 
> Thoughts ?

Why define an architectural "struct rmpentry"?  IIUC, there isn't a true
architectural RMP entry; the APM defines architectural fields but doesn't define
a layout.  Functionally, making up our own struct isn't a problem, I just don't
see the point since all use cases only care about Assigned and Page-Size, and
we can do them a favor by translating Page-Size to X86_PG_LEVEL.

> >> /*
> >>   * Returns 1 if the RMP entry is assigned, 0 if it exists but is not
> >>   * assigned, and -errno if there is no corresponding RMP entry.
> >>   */
> >> int snp_lookup_rmpentry(struct page *page, int *level)
> >> {
> >>     unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
> >>     struct rmpentry *entry, *large_entry;
> >>     unsigned long vaddr;
> >>
> >>     if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
> >>         return -ENXIO;
> >>
> >>     vaddr = rmptable_start + rmptable_page_offset(phys);
> >>     if (unlikely(vaddr > rmptable_end))
> >>         return -EXNIO;
> >>
> >>     entry = (struct rmpentry *)vaddr;
> >>
> >>     /* Read a large RMP entry to get the correct page level used in
> >> RMP entry. */
> >>     vaddr = rmptable_start + rmptable_page_offset(phys & PMD_MASK);
> >>     large_entry = (struct rmpentry *)vaddr;
> >>     *level = RMP_TO_X86_PG_LEVEL(rmpentry_pagesize(large_entry));
> >>
> >>     return !!entry->assigned;
> >> }
> >>
> >>
> >> And then move dump_rmpentry() (or add a helper) in sev.c so that "struct
> >> rmpentry" can be declared in sev.c.
Brijesh Singh July 20, 2021, 11:10 p.m. UTC | #5
On 7/20/21 5:06 PM, Sean Christopherson wrote:
> On Fri, Jul 16, 2021, Brijesh Singh wrote:
>> On 7/15/21 2:28 PM, Brijesh Singh wrote:
>>>> Looking at the future patches, dump_rmpentry() is the only power user,
>>>> e.g.  everything else mostly looks at "assigned" and "level" (and one
>>>> ratelimited warn on "validated" in snp_make_page_shared(), but I suspect
>>>> that particular check can and should be dropped).
>>> Yes, we need "assigned" and "level" and other entries are mainly for
>>> the debug purposes.
>>>
>> For the debug purposes, we would like to dump additional RMP entries. If
>> we go with your proposed function then how do we get those information
>> in the dump_rmpentry()?
> As suggested below, move dump_rmpentry() into sev.c so that it can use the
> microarchitectural version.  For debug, I'm pretty that's what we'll want anyways,
> e.g. dump the raw value along with the meaning of various bits.


Based on other feedbacks, I am not sure if we need to dump the RMP
entry;  In other feedback we agreed to unmap the pages from the direct
map while adding them in the RMP table, so, if anyone attempts to access
those pages they will now get the page-not-present instead of the RMP
violation.

thanks
diff mbox series

Patch

diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
index 6c23e694a109..9e7e7e737f55 100644
--- a/arch/x86/include/asm/sev.h
+++ b/arch/x86/include/asm/sev.h
@@ -9,6 +9,7 @@ 
 #define __ASM_ENCRYPTED_STATE_H
 
 #include <linux/types.h>
+#include <linux/sev.h>
 #include <asm/insn.h>
 #include <asm/sev-common.h>
 #include <asm/bootparam.h>
@@ -75,9 +76,6 @@  extern bool handle_vc_boot_ghcb(struct pt_regs *regs);
 /* Software defined (when rFlags.CF = 1) */
 #define PVALIDATE_FAIL_NOUPDATE		255
 
-/* RMP page size */
-#define RMP_PG_SIZE_4K			0
-
 #define RMPADJUST_VMSA_PAGE_BIT		BIT(16)
 
 #ifdef CONFIG_AMD_MEM_ENCRYPT
diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
index f9d813d498fa..1aed3d53f59f 100644
--- a/arch/x86/kernel/sev.c
+++ b/arch/x86/kernel/sev.c
@@ -49,6 +49,8 @@ 
 #define DR7_RESET_VALUE        0x400
 
 #define RMPTABLE_ENTRIES_OFFSET        0x4000
+#define RMPENTRY_SHIFT			8
+#define rmptable_page_offset(x)	(RMPTABLE_ENTRIES_OFFSET + (((unsigned long)x) >> RMPENTRY_SHIFT))
 
 /* For early boot hypervisor communication in SEV-ES enabled guests */
 static struct ghcb boot_ghcb_page __bss_decrypted __aligned(PAGE_SIZE);
@@ -2319,3 +2321,27 @@  static int __init snp_rmptable_init(void)
  * passthough state, and it is available after subsys_initcall().
  */
 fs_initcall(snp_rmptable_init);
+
+struct rmpentry *snp_lookup_page_in_rmptable(struct page *page, int *level)
+{
+	unsigned long phys = page_to_pfn(page) << PAGE_SHIFT;
+	struct rmpentry *entry, *large_entry;
+	unsigned long vaddr;
+
+	if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
+		return NULL;
+
+	vaddr = rmptable_start + rmptable_page_offset(phys);
+	if (unlikely(vaddr > rmptable_end))
+		return NULL;
+
+	entry = (struct rmpentry *)vaddr;
+
+	/* Read a large RMP entry to get the correct page level used in RMP entry. */
+	vaddr = rmptable_start + rmptable_page_offset(phys & PMD_MASK);
+	large_entry = (struct rmpentry *)vaddr;
+	*level = RMP_TO_X86_PG_LEVEL(rmpentry_pagesize(large_entry));
+
+	return entry;
+}
+EXPORT_SYMBOL_GPL(snp_lookup_page_in_rmptable);
diff --git a/include/linux/sev.h b/include/linux/sev.h
new file mode 100644
index 000000000000..83c89e999999
--- /dev/null
+++ b/include/linux/sev.h
@@ -0,0 +1,51 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Secure Encrypted Virtualization
+ *
+ * Author: Brijesh Singh <brijesh.singh@amd.com>
+ */
+
+#ifndef __LINUX_SEV_H
+#define __LINUX_SEV_H
+
+struct __packed rmpentry {
+	union {
+		struct {
+			u64	assigned	: 1,
+				pagesize	: 1,
+				immutable	: 1,
+				rsvd1		: 9,
+				gpa		: 39,
+				asid		: 10,
+				vmsa		: 1,
+				validated	: 1,
+				rsvd2		: 1;
+		} info;
+		u64 low;
+	};
+	u64 high;
+};
+
+#define rmpentry_assigned(x)	((x)->info.assigned)
+#define rmpentry_pagesize(x)	((x)->info.pagesize)
+#define rmpentry_vmsa(x)	((x)->info.vmsa)
+#define rmpentry_asid(x)	((x)->info.asid)
+#define rmpentry_validated(x)	((x)->info.validated)
+#define rmpentry_gpa(x)		((unsigned long)(x)->info.gpa)
+#define rmpentry_immutable(x)	((x)->info.immutable)
+
+/* RMP page size */
+#define RMP_PG_SIZE_4K			0
+
+#define RMP_TO_X86_PG_LEVEL(level)	(((level) == RMP_PG_SIZE_4K) ? PG_LEVEL_4K : PG_LEVEL_2M)
+
+#ifdef CONFIG_AMD_MEM_ENCRYPT
+struct rmpentry *snp_lookup_page_in_rmptable(struct page *page, int *level);
+#else
+static inline struct rmpentry *snp_lookup_page_in_rmptable(struct page *page, int *level)
+{
+	return NULL;
+}
+
+#endif /* CONFIG_AMD_MEM_ENCRYPT */
+#endif /* __LINUX_SEV_H */