diff mbox series

[v6,14/42] x86/sev: Register GHCB memory when SEV-SNP is active

Message ID 20211008180453.462291-15-brijesh.singh@amd.com (mailing list archive)
State New, archived
Headers show
Series Add AMD Secure Nested Paging (SEV-SNP) Guest Support | expand

Commit Message

Brijesh Singh Oct. 8, 2021, 6:04 p.m. UTC
The SEV-SNP guest is required to perform GHCB GPA registration. This is
because the hypervisor may prefer that a guest use a consistent and/or
specific GPA for the GHCB associated with a vCPU. For more information,
see the GHCB specification section GHCB GPA Registration.

During the boot, init_ghcb() allocates a per-cpu GHCB page. On very first
VC exception, the exception handler switch to using the per-cpu GHCB page
allocated during the init_ghcb(). The GHCB page must be registered in
the current vcpu context.

Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
 arch/x86/kernel/sev.c | 124 +++++++++++++++++++++++++-----------------
 1 file changed, 75 insertions(+), 49 deletions(-)

Comments

Borislav Petkov Nov. 2, 2021, 4:53 p.m. UTC | #1
On Fri, Oct 08, 2021 at 01:04:25PM -0500, Brijesh Singh wrote:
> +	/* SEV-SNP guest requires that GHCB must be registered. */
> +	if (cc_platform_has(CC_ATTR_SEV_SNP))
> +		snp_register_ghcb(data, __pa(ghcb));

This looks like more of that "let's register a GHCB at the time the
first #VC fires".

And there already is setup_ghcb() which is called in the #VC handler.
And that thing registers a GHCB GPA.

But then you have to do it here again.

I think this should be changed together with the CPUID page detection
stuff we talked about earlier, where, after you've established that this
is an SNP guest, you call setup_ghcb() *once* and after that you have
everything set up, including the GHCB GPA. And then the #VC exceptions
can come.

Right?

Or is there a chicken-and-an-egg issue here which I'm not thinking
about?
Brijesh Singh Nov. 2, 2021, 6:24 p.m. UTC | #2
Hi Boris,


On 11/2/21 11:53 AM, Borislav Petkov wrote:
> On Fri, Oct 08, 2021 at 01:04:25PM -0500, Brijesh Singh wrote:
>> +	/* SEV-SNP guest requires that GHCB must be registered. */
>> +	if (cc_platform_has(CC_ATTR_SEV_SNP))
>> +		snp_register_ghcb(data, __pa(ghcb));
> 
> This looks like more of that "let's register a GHCB at the time the
> first #VC fires".
> 

There are two #VC handlers:

1) early exception handler [do_vc_no_ghcb()]. The handler uses the MSR 
protocol based VMGEXIT.

https://elixir.bootlin.com/linux/latest/source/arch/x86/kernel/sev-shared.c#L147


2) exception handler setup during the idt bringup 
[handle_vc_boot_ghcb()]. The handler uses the full GHCB.
https://elixir.bootlin.com/linux/latest/source/arch/x86/kernel/sev.c#L1472

To answer your question, GHCB is registered at the time of first #VC 
handling by the second exception handler. Mike can correct me, the CPUID 
page check is going to happen on first #VC handling inside the early 
exception handler (i.e case 1). The early exception handler uses the MSR 
protocol, so, there is no need to register the GHCB page. Before 
registering the page we need to map it unencrypted.


> And there already is setup_ghcb() which is called in the #VC handler.
> And that thing registers a GHCB GPA.
> 

There are two cases that need to be covered 1) BSP GHCB page and 2) APs 
GHCB page. The setup_ghcb() is called for the BSP. Later on, per-cpu 
GHCB page is used by the APs. APs need to register their GHCB page 
before using it.

> But then you have to do it here again.
> 
> I think this should be changed together with the CPUID page detection
> stuff we talked about earlier, where, after you've established that this
> is an SNP guest, you call setup_ghcb() *once* and after that you have
> everything set up, including the GHCB GPA. And then the #VC exceptions
> can come.

See if my above explanation make sense. Based on it, I don't think it 
makes sense to register the GHCB during the CPUID page detection. The 
CPUID page detection will occur in early VC handling.

> Right?
> 
> Or is there a chicken-and-an-egg issue here which I'm not thinking
> about?
>
Borislav Petkov Nov. 2, 2021, 6:44 p.m. UTC | #3
On Tue, Nov 02, 2021 at 01:24:01PM -0500, Brijesh Singh wrote:
> To answer your question, GHCB is registered at the time of first #VC
> handling by the second exception handler.

And this is what I don't like - register at use. Instead of init
everything *before* use.

> Mike can correct me, the CPUID page check is going to happen on first
> #VC handling inside the early exception handler (i.e case 1).

What is the "CPUID page check"?

And no, you don't want to do any detection when an exception happens -
you want to detect *everything* *first* and then do exceptions.

> See if my above explanation make sense. Based on it, I don't think it
> makes sense to register the GHCB during the CPUID page detection. The
> CPUID page detection will occur in early VC handling.

See above. If this needs more discussion, we can talk on IRC.
Brijesh Singh Nov. 3, 2021, 8:10 p.m. UTC | #4
Hi Boris,


On 11/2/21 1:44 PM, Borislav Petkov wrote:
> On Tue, Nov 02, 2021 at 01:24:01PM -0500, Brijesh Singh wrote:
>> To answer your question, GHCB is registered at the time of first #VC
>> handling by the second exception handler.
> 
> And this is what I don't like - register at use. Instead of init
> everything *before* use.
> 
>> Mike can correct me, the CPUID page check is going to happen on first
>> #VC handling inside the early exception handler (i.e case 1).
> 
> What is the "CPUID page check"?
> 
> And no, you don't want to do any detection when an exception happens -
> you want to detect *everything* *first* and then do exceptions.
> 
>> See if my above explanation make sense. Based on it, I don't think it
>> makes sense to register the GHCB during the CPUID page detection. The
>> CPUID page detection will occur in early VC handling.
> 
> See above. If this needs more discussion, we can talk on IRC.
> 

Looking at the secondary CPU bring up path it seems that we will not be 
getting #VC until the early_setup_idt() is called. I am thinking to add 
function to register the GHCB from the early_setup_idt()

early_setup_idt()
{
   ...
   if (IS_ENABLED(CONFIG_MEM_ENCRYPT))
     sev_snp_register_ghcb()
   ...
}

The above will cover the APs and for BSP case I can call the same 
function just after the final IDT is loaded

cpu_init_exception_handling()
{
    ...
    ...
    /* Finally load the IDT */
    load_current_idt();

    if (IS_ENABLED(CONFIG_MEM_ENCRYPT))
      sev_snp_register_ghcb()

}

Please let me know if something like above is acceptable.

thanks
Borislav Petkov Nov. 4, 2021, 1:58 p.m. UTC | #5
On Wed, Nov 03, 2021 at 03:10:16PM -0500, Brijesh Singh wrote:
> Looking at the secondary CPU bring up path it seems that we will not be
> getting #VC until the early_setup_idt() is called. I am thinking to add
> function to register the GHCB from the early_setup_idt()
> 
> early_setup_idt()
> {
>   ...
>   if (IS_ENABLED(CONFIG_MEM_ENCRYPT))
>     sev_snp_register_ghcb()
>   ...
> }
> 
> The above will cover the APs

That will cover the APs during early boot as that is being called from
asm.

> and for BSP case I can call the same function just after the final IDT
> is loaded

Why after and not before?

> cpu_init_exception_handling()
> {
>    ...
>    ...
>    /* Finally load the IDT */
>    load_current_idt();
> 
>    if (IS_ENABLED(CONFIG_MEM_ENCRYPT))
>      sev_snp_register_ghcb()
> 
> }

That is also called on the APs - not only the BSP. trap_init() calls it
from start_kernel() which is the BSP and cpu_init_secondary() calls it
too, which is ofc the APs.

I guess that should be ok since you're calling the same function from
both but WTH do I know...
Brijesh Singh Nov. 4, 2021, 3:26 p.m. UTC | #6
On 11/4/21 8:58 AM, Borislav Petkov wrote:
> On Wed, Nov 03, 2021 at 03:10:16PM -0500, Brijesh Singh wrote:
>> Looking at the secondary CPU bring up path it seems that we will not be
>> getting #VC until the early_setup_idt() is called. I am thinking to add
>> function to register the GHCB from the early_setup_idt()
>>
>> early_setup_idt()
>> {
>>    ...
>>    if (IS_ENABLED(CONFIG_MEM_ENCRYPT))
>>      sev_snp_register_ghcb()
>>    ...
>> }
>>
>> The above will cover the APs
> 
> That will cover the APs during early boot as that is being called from
> asm.
> 
>> and for BSP case I can call the same function just after the final IDT
>> is loaded
> 
> Why after and not before?
> 

I just looked at load_current_idt() and we should not get #VC before 
loading the new idt, so, its safe to do is before.


>> cpu_init_exception_handling()
>> {
>>     ...
>>     ...
>>     /* Finally load the IDT */
>>     load_current_idt();
>>
>>     if (IS_ENABLED(CONFIG_MEM_ENCRYPT))
>>       sev_snp_register_ghcb()
>>
>> }
> 
> That is also called on the APs - not only the BSP. trap_init() calls it
> from start_kernel() which is the BSP and cpu_init_secondary() calls it
> too, which is ofc the APs.
> 
> I guess that should be ok since you're calling the same function from
> both but WTH do I know...
> 

For AP case, we will be registering the same GHCB GPA twice, that should 
not be an issue. The GHCB spec does not restrict us on registering the 
GPA twice.

Of course, the current patch does not suffer with it. Let me know your 
preference.

thanks
Borislav Petkov Nov. 4, 2021, 4:03 p.m. UTC | #7
On November 4, 2021 3:26:56 PM UTC, Brijesh Singh <brijesh.singh@amd.com> wrote:
>Of course, the current patch does not suffer with it. Let me know your 
>preference.

Whatever keeps the code simpler.

Thx.
diff mbox series

Patch

diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
index 2290fbcc1844..4c891d5d9651 100644
--- a/arch/x86/kernel/sev.c
+++ b/arch/x86/kernel/sev.c
@@ -77,6 +77,13 @@  struct sev_es_runtime_data {
 	 * is currently unsupported in SEV-ES guests.
 	 */
 	unsigned long dr7;
+
+	/*
+	 * SEV-SNP requires that the GHCB must be registered before using it.
+	 * The flag below will indicate whether the GHCB is registered, if its
+	 * not registered then sev_es_get_ghcb() will perform the registration.
+	 */
+	bool snp_ghcb_registered;
 };
 
 struct ghcb_state {
@@ -160,55 +167,6 @@  void noinstr __sev_es_ist_exit(void)
 	this_cpu_write(cpu_tss_rw.x86_tss.ist[IST_INDEX_VC], *(unsigned long *)ist);
 }
 
-/*
- * Nothing shall interrupt this code path while holding the per-CPU
- * GHCB. The backup GHCB is only for NMIs interrupting this path.
- *
- * Callers must disable local interrupts around it.
- */
-static noinstr struct ghcb *__sev_get_ghcb(struct ghcb_state *state)
-{
-	struct sev_es_runtime_data *data;
-	struct ghcb *ghcb;
-
-	WARN_ON(!irqs_disabled());
-
-	data = this_cpu_read(runtime_data);
-	ghcb = &data->ghcb_page;
-
-	if (unlikely(data->ghcb_active)) {
-		/* GHCB is already in use - save its contents */
-
-		if (unlikely(data->backup_ghcb_active)) {
-			/*
-			 * Backup-GHCB is also already in use. There is no way
-			 * to continue here so just kill the machine. To make
-			 * panic() work, mark GHCBs inactive so that messages
-			 * can be printed out.
-			 */
-			data->ghcb_active        = false;
-			data->backup_ghcb_active = false;
-
-			instrumentation_begin();
-			panic("Unable to handle #VC exception! GHCB and Backup GHCB are already in use");
-			instrumentation_end();
-		}
-
-		/* Mark backup_ghcb active before writing to it */
-		data->backup_ghcb_active = true;
-
-		state->ghcb = &data->backup_ghcb;
-
-		/* Backup GHCB content */
-		*state->ghcb = *ghcb;
-	} else {
-		state->ghcb = NULL;
-		data->ghcb_active = true;
-	}
-
-	return ghcb;
-}
-
 /* Needed in vc_early_forward_exception */
 void do_early_exception(struct pt_regs *regs, int trapnr);
 
@@ -464,6 +422,69 @@  static enum es_result vc_slow_virt_to_phys(struct ghcb *ghcb, struct es_em_ctxt
 /* Include code shared with pre-decompression boot stage */
 #include "sev-shared.c"
 
+static void snp_register_ghcb(struct sev_es_runtime_data *data, unsigned long paddr)
+{
+	if (data->snp_ghcb_registered)
+		return;
+
+	snp_register_ghcb_early(paddr);
+
+	data->snp_ghcb_registered = true;
+}
+
+/*
+ * Nothing shall interrupt this code path while holding the per-CPU
+ * GHCB. The backup GHCB is only for NMIs interrupting this path.
+ *
+ * Callers must disable local interrupts around it.
+ */
+static noinstr struct ghcb *__sev_get_ghcb(struct ghcb_state *state)
+{
+	struct sev_es_runtime_data *data;
+	struct ghcb *ghcb;
+
+	WARN_ON(!irqs_disabled());
+
+	data = this_cpu_read(runtime_data);
+	ghcb = &data->ghcb_page;
+
+	if (unlikely(data->ghcb_active)) {
+		/* GHCB is already in use - save its contents */
+
+		if (unlikely(data->backup_ghcb_active)) {
+			/*
+			 * Backup-GHCB is also already in use. There is no way
+			 * to continue here so just kill the machine. To make
+			 * panic() work, mark GHCBs inactive so that messages
+			 * can be printed out.
+			 */
+			data->ghcb_active        = false;
+			data->backup_ghcb_active = false;
+
+			instrumentation_begin();
+			panic("Unable to handle #VC exception! GHCB and Backup GHCB are already in use");
+			instrumentation_end();
+		}
+
+		/* Mark backup_ghcb active before writing to it */
+		data->backup_ghcb_active = true;
+
+		state->ghcb = &data->backup_ghcb;
+
+		/* Backup GHCB content */
+		*state->ghcb = *ghcb;
+	} else {
+		state->ghcb = NULL;
+		data->ghcb_active = true;
+	}
+
+	/* SEV-SNP guest requires that GHCB must be registered. */
+	if (cc_platform_has(CC_ATTR_SEV_SNP))
+		snp_register_ghcb(data, __pa(ghcb));
+
+	return ghcb;
+}
+
 static noinstr void __sev_put_ghcb(struct ghcb_state *state)
 {
 	struct sev_es_runtime_data *data;
@@ -650,6 +671,10 @@  static bool __init setup_ghcb(void)
 	/* Alright - Make the boot-ghcb public */
 	boot_ghcb = &boot_ghcb_page;
 
+	/* SEV-SNP guest requires that GHCB GPA must be registered. */
+	if (cc_platform_has(CC_ATTR_SEV_SNP))
+		snp_register_ghcb_early(__pa(&boot_ghcb_page));
+
 	return true;
 }
 
@@ -739,6 +764,7 @@  static void __init init_ghcb(int cpu)
 
 	data->ghcb_active = false;
 	data->backup_ghcb_active = false;
+	data->snp_ghcb_registered = false;
 }
 
 void __init sev_es_init_vc_handling(void)