diff mbox

[Part2,v6,13/38] crypto: ccp: Add Secure Encrypted Virtualization (SEV) command support

Message ID 20171020023413.122280-14-brijesh.singh@amd.com (mailing list archive)
State Not Applicable
Delegated to: Herbert Xu
Headers show

Commit Message

Brijesh Singh Oct. 20, 2017, 2:33 a.m. UTC
AMD's new Secure Encrypted Virtualization (SEV) feature allows the
memory contents of virtual machines to be transparently encrypted with a
key unique to the VM. The programming and management of the encryption
keys are handled by the AMD Secure Processor (AMD-SP) which exposes the
commands for these tasks. The complete spec is available at:

http://support.amd.com/TechDocs/55766_SEV-KM%20API_Specification.pdf

Extend the AMD-SP driver to provide the following support:

 - an in-kernel API to communicate with the SEV firmware. The API can be
   used by the hypervisor to create encryption context for a SEV guest.

 - a userspace IOCTL to manage the platform certificates.

Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: "Radim Krčmář" <rkrcmar@redhat.com>
Cc: Borislav Petkov <bp@suse.de>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: Gary Hook <gary.hook@amd.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: linux-crypto@vger.kernel.org
Cc: kvm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Improvements-by: Borislav Petkov <bp@suse.de>
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
 drivers/crypto/ccp/psp-dev.c | 306 +++++++++++++++++++++++++++++++++++++++++++
 drivers/crypto/ccp/psp-dev.h |  21 +++
 include/linux/psp-sev.h      | 159 ++++++++++++++++++++++
 3 files changed, 486 insertions(+)

Comments

Borislav Petkov Oct. 23, 2017, 9:20 a.m. UTC | #1
On Thu, Oct 19, 2017 at 09:33:48PM -0500, Brijesh Singh wrote:
> +static int __sev_platform_init(struct sev_data_init *data, int *error)
> +{
> +	int rc = 0;
> +
> +	mutex_lock(&fw_init_mutex);
> +
> +	if (!fw_init_count) {

I still don't like global semaphores. Can you get the status and check
for PSTATE.INIT state and do the init only if the platform is in
PSTATE.UNINIT?
Brijesh Singh Oct. 23, 2017, 7:57 p.m. UTC | #2
On 10/23/2017 04:20 AM, Borislav Petkov wrote:
> On Thu, Oct 19, 2017 at 09:33:48PM -0500, Brijesh Singh wrote:
>> +static int __sev_platform_init(struct sev_data_init *data, int *error)
>> +{
>> +	int rc = 0;
>> +
>> +	mutex_lock(&fw_init_mutex);
>> +
>> +	if (!fw_init_count) {
> 
> I still don't like global semaphores. Can you get the status and check
> for PSTATE.INIT state and do the init only if the platform is in
> PSTATE.UNINIT?
> 


Calling PLATFORM_GET_STATUS is not required, we can manage the state 
through a simple ref count variable. Issuing PSP commands will always be 
much more expensive compare to accessing a protected global variable. I 
would prefer to avoid invoking PSP command if possible. Additionally, 
the global semaphore is still needed to serialize the 
sev_platform_init() and sev_platform_shutdown() from multiple processes. 
e.g If process "A" calls sev_platform_init() and if it gets preempted 
due to whatever reason then we don't want another process to issue the 
shutdown command while process "A" is in middle of sev_platform_init().

-Brijesh
Borislav Petkov Oct. 26, 2017, 1:56 p.m. UTC | #3
On Mon, Oct 23, 2017 at 02:57:04PM -0500, Brijesh Singh wrote:
> Calling PLATFORM_GET_STATUS is not required, we can manage the state through
> a simple ref count variable. Issuing PSP commands will always be much more
> expensive compare to accessing a protected global variable.

What does "protected" mean here?

In any case, that variable can be a simple bool as you use it as such.

> I would prefer to avoid invoking PSP command if possible.
> Additionally, the global semaphore is still needed to serialize
> the sev_platform_init() and sev_platform_shutdown() from multiple
> processes. e.g If process "A" calls sev_platform_init() and if it gets
> preempted due to whatever reason then we don't want another process
> to issue the shutdown command while process "A" is in middle of
> sev_platform_init().

How? You're holding fw_init_mutex.
Brijesh Singh Oct. 26, 2017, 4:56 p.m. UTC | #4
On 10/26/2017 08:56 AM, Borislav Petkov wrote:
> On Mon, Oct 23, 2017 at 02:57:04PM -0500, Brijesh Singh wrote:
>> Calling PLATFORM_GET_STATUS is not required, we can manage the state through
>> a simple ref count variable. Issuing PSP commands will always be much more
>> expensive compare to accessing a protected global variable.
> 
> What does "protected" mean here?
> 

Access global variable after acquiring the semaphore.


> In any case, that variable can be a simple bool as you use it as such.
> 

I am not using the variable (fw_init_count) as boolean. The variable 
gets incremented in sev_platform_init() and decremented in 
sev_platform_shutdown(). In very first call to sev_platform_init (i.e 
when variable is zero) we issue  PLATFORM_INIT command, similarly 
PLATFORM_SHUTDOWN is issued on the last (i.e when variable value is 
reached to zero). The variable is used as ref counter.


>> I would prefer to avoid invoking PSP command if possible.
>> Additionally, the global semaphore is still needed to serialize
>> the sev_platform_init() and sev_platform_shutdown() from multiple
>> processes. e.g If process "A" calls sev_platform_init() and if it gets
>> preempted due to whatever reason then we don't want another process
>> to issue the shutdown command while process "A" is in middle of
>> sev_platform_init().
> 
> How? You're holding fw_init_mutex.
> 

In your previous reply you comments on global semaphore (fw_init_mutex) 
and in response I tried to highlight why we need the global semaphore. 
Did I misunderstood your comment ?

-Brijesh
Borislav Petkov Oct. 26, 2017, 5:44 p.m. UTC | #5
On Thu, Oct 26, 2017 at 11:56:57AM -0500, Brijesh Singh wrote:
> The variable is used as ref counter.

... and it can't be converted to a boolean because...?

> In your previous reply you comments on global semaphore (fw_init_mutex) and
> in response I tried to highlight why we need the global semaphore. Did I
> misunderstood your comment ?

Yes, what happens if you get preempted while holding the mutex? Will the other
process be able to do anything?
Brijesh Singh Oct. 26, 2017, 7:26 p.m. UTC | #6
On 10/26/2017 12:44 PM, Borislav Petkov wrote:
> On Thu, Oct 26, 2017 at 11:56:57AM -0500, Brijesh Singh wrote:
>> The variable is used as ref counter.
> 
> ... and it can't be converted to a boolean because...?
> 

SHUTDOWN command unconditionally transitions a platform to uninitialized 
state. The command does not care how many processes are actively using 
the PSP. We don't want to shutdown the firmware while other process is 
still using it.

e.g consider three processes (A, B, C)

Process A:
----------
sev_platform_init()
sev_do_cmd(..)
...
...
sev_do_cmd(..)
...
sev_platform_shutdown()

Process B:
-----------
sev_platform_init()
sev_do_cmd(...)
sev_platform_shutdown()

Process C:
----------
sev_platform_init()
sev_do_cmd(...)
sev_do_cmd(...)
sev_do_cmd(...)
sev_platform_shutdown()

As per the SEV spec section 5.1.2 (platform state machine), several 
commands require that platform should be initialized before issuing the 
actual command. As you can see Process B may finish quickly and SHUTDOWN 
from process B will simply uninitialize the firmware and cause 
unexpected result to process A and C.


>> In your previous reply you comments on global semaphore (fw_init_mutex) and
>> in response I tried to highlight why we need the global semaphore. Did I
>> misunderstood your comment ?
> 
> Yes, what happens if you get preempted while holding the mutex? Will the other
> process be able to do anything?
> 

If other process tries to issue the sev_platform_init/shutdown() then 
they have to wait.

The sev_platform_init() and sev_platform_shutdown() uses the same global 
mutex. See the original code below.

+static int __sev_platform_init(struct sev_data_init *data, int *error)
+{
+	int rc = 0;
+
+	mutex_lock(&fw_init_mutex);
+
+	if (!fw_init_count) {
+		rc = sev_do_cmd(SEV_CMD_INIT, data, error);
+		if (rc)
+			goto unlock;
+	}
+
+	fw_init_count++;
+
+unlock:
+	mutex_unlock(&fw_init_mutex);
+	return rc;
+
+}
+
+int sev_platform_shutdown(int *error)
+{
+	int rc = 0;
+
+	mutex_lock(&fw_init_mutex);
+
+	if (!fw_init_count)
+		goto unlock;
+
+	if (fw_init_count == 1) {
+		rc = sev_do_cmd(SEV_CMD_SHUTDOWN, 0, error);
+		if (rc)
+			goto unlock;
+	}
+
+	fw_init_count--;
+
+unlock:
+	mutex_unlock(&fw_init_mutex);
+	return rc;
+}
Borislav Petkov Oct. 26, 2017, 8:13 p.m. UTC | #7
On Thu, Oct 26, 2017 at 02:26:15PM -0500, Brijesh Singh wrote:
> SHUTDOWN command unconditionally transitions a platform to uninitialized
> state. The command does not care how many processes are actively using the
> PSP. We don't want to shutdown the firmware while other process is still
> using it.

So why do you have to init and shutdown the PSP each time you execute a
command? Why isn't the PSP initialized, *exactly* *once* at driver init
and shut down, also exactly once at driver exit?

> If other process tries to issue the sev_platform_init/shutdown() then they
> have to wait.

Exactly, and not what you said earlier:

"If process "A" calls sev_platform_init() and if it gets preempted due
to whatever reason then we don't want another process to issue the
shutdown command while process "A" is in middle of sev_platform_init()."

IOW, if your critical regions are protected properly by a mutex, nothing
like the above will happen.

But what you're trying to explain to me is that the fw_init_count is
going to prevent a premature shutdown when it is > 1. But that's not
what I meant...

Anyway, see my question above.
Brijesh Singh Oct. 26, 2017, 8:59 p.m. UTC | #8
On 10/26/2017 03:13 PM, Borislav Petkov wrote:
> On Thu, Oct 26, 2017 at 02:26:15PM -0500, Brijesh Singh wrote:
>> SHUTDOWN command unconditionally transitions a platform to uninitialized
>> state. The command does not care how many processes are actively using the
>> PSP. We don't want to shutdown the firmware while other process is still
>> using it.
> 
> So why do you have to init and shutdown the PSP each time you execute a
> command? Why isn't the PSP initialized, *exactly* *once* at driver init
> and shut down, also exactly once at driver exit?

Wish we could do that but the following reasons makes things complicated:

1) The commands must be issued from the PSP master devices, at PSP 
initialization time we do not know the PSP 'master' device. Hence we 
will not able to invoke sev_platform_init() during the PSP 
initialization time.

2) some commands require the platform to be in UNINIT state -- e.g 
FACTORY_RESET. So, if we do the INIT at the PSP initialization time then 
we still need to perform the SHUTDOWN outside the normal code flow to 
handle these commands.

we can workaround #1 by adding some hooks in sp_pci_init() to invoke the 
PSP initialization routines after pci_register_driver() is done but #2 
can get painful because it will require us calling the SHUTDOWN outside 
the sp_pci_exit() code flow.


-Brijesh
Borislav Petkov Oct. 27, 2017, 7:56 a.m. UTC | #9
On Thu, Oct 26, 2017 at 03:59:32PM -0500, Brijesh Singh wrote:
> we can workaround #1 by adding some hooks in sp_pci_init() to invoke the PSP
> initialization routines after pci_register_driver() is done but #2 can get
> painful because it will require us calling the SHUTDOWN outside the
> sp_pci_exit() code flow.

Ok, do that and init the PSP master and then put the device in UNINIT
state only in the functions which execute those commands which need the
device to be in UNINIT state, e.g., wrap the SEV_CMD_FACTORY_RESET glue
in a command function which does put the device in the UNINIT state as a
first step.

Then, when that function is done, put the device in the mode which the
other commands would expect it to be in, e.g., INIT state.

This way you'll simplify the whole command flow considerably and won't
have to "toggle" the device each time and will save yourself a lot of
time on command execution.

Thx.
Brijesh Singh Oct. 27, 2017, 11:28 a.m. UTC | #10
On 10/27/17 2:56 AM, Borislav Petkov wrote:
> On Thu, Oct 26, 2017 at 03:59:32PM -0500, Brijesh Singh wrote:
>> we can workaround #1 by adding some hooks in sp_pci_init() to invoke the PSP
>> initialization routines after pci_register_driver() is done but #2 can get
>> painful because it will require us calling the SHUTDOWN outside the
>> sp_pci_exit() code flow.
> Ok, do that and init the PSP master and then put the device in UNINIT
> state only in the functions which execute those commands which need the
> device to be in UNINIT state, e.g., wrap the SEV_CMD_FACTORY_RESET glue
> in a command function which does put the device in the UNINIT state as a
> first step.

transiting a platform in UINIT state to handle the FACTORY_RESET can
have a negative consequence.

Consider this scenario:

Process A
---------
sev_launch_start(...)

while (count < 10000) {
    sev_launch_update(...)
}

sev_launch_finish()
...
...

Process B:
---------
....
sev_factory_reset();
....

If in order to handle the FACTORY_RESET we  transition a platform in
UINIT state then it will results as unexpected failure from the
sev_launch_update() because the FACTORY_RESET command remove all the
state information created by sev_launch_start() etc. 

I think our design so far is simple, if command require INIT state then
caller executes sev_platform_init(), then command and finish with
sev_platform_shutdown(). If command does not require INIT state, then
simply issue the command. e.g currently, when caller issues
FACTORY_RESET then we pass command directly to PSP and if FW is in INIT
state then FACTORY_RESET returns error (INVALID_STATE/EBUSY) and we
propagate the error code to userspace.  User can retry the command
sometime later when nobody else is using the PSP.


>
> Then, when that function is done, put the device in the mode which the
> other commands would expect it to be in, e.g., INIT state.
>
> This way you'll simplify the whole command flow considerably and won't
> have to "toggle" the device each time and will save yourself a lot of
> time on command execution.
>
> Thx.
>
Borislav Petkov Oct. 27, 2017, 8:15 p.m. UTC | #11
On Fri, Oct 27, 2017 at 06:28:38AM -0500, Brijesh Singh wrote:
> ... User can retry the command sometime later when nobody else is
> using the PSP.

That still doesn't prevent you from doing two things:

* make that fw_init_count a proper kref instead of your homegrown thing

* do not preemptively execute commands on the PSP if you can't possibly
know what the next command is going to be - instead, just put it in the
required state only when you really have to. I.e., don't do all that
unnecessary INIT -> CMD -> SHUTDOWN game for no reason.

Thx.
Brijesh Singh Oct. 27, 2017, 8:25 p.m. UTC | #12
On 10/27/17 3:15 PM, Borislav Petkov wrote:
> On Fri, Oct 27, 2017 at 06:28:38AM -0500, Brijesh Singh wrote:
>> ... User can retry the command sometime later when nobody else is
>> using the PSP.
> That still doesn't prevent you from doing two things:
>
> * make that fw_init_count a proper kref instead of your homegrown thing

OK, I can use kref in next patch.

>
> * do not preemptively execute commands on the PSP if you can't possibly
> know what the next command is going to be - instead, just put it in the
> required state only when you really have to. I.e., don't do all that
> unnecessary INIT -> CMD -> SHUTDOWN game for no reason.

Yep, we are doing state transition only when we really need to. At least
so far I have tried to avoid making any unnecessary state transitions.

> Thx.
>
Borislav Petkov Oct. 27, 2017, 8:27 p.m. UTC | #13
On Fri, Oct 27, 2017 at 03:25:24PM -0500, Brijesh Singh wrote:
> Yep, we are doing state transition only when we really need to. At least
> so far I have tried to avoid making any unnecessary state transitions.

So change all those which do INIT -> CMD -> SHUTDOWN to do only the
command as the INIT state is the default state, AFAIU it.
Brijesh Singh Oct. 27, 2017, 9:28 p.m. UTC | #14
On 10/27/17 3:27 PM, Borislav Petkov wrote:
> On Fri, Oct 27, 2017 at 03:25:24PM -0500, Brijesh Singh wrote:
>> Yep, we are doing state transition only when we really need to. At least
>> so far I have tried to avoid making any unnecessary state transitions.
> So change all those which do INIT -> CMD -> SHUTDOWN to do only the
> command as the INIT state is the default state, AFAIU it.
>
I don't that will work. It may be possible that I am not able to follow
what exactly you have in mind. Please let me clarify it, on boot the
firmware is in UINIT state.

Firmware maintain three states: UINIT, INIT and WORKING. Few commands
can be executed in UINIT, several command can be executed in INIT, some
command can be executed in WORKING and few commands can be executed in
any states (see SEV spec for platform state). We do not have ioctls to
issue the INIT and SHUTDOWN command.

As I have explained in previous messages, the SHUTDOWN unconditionally
destory's the FW context hence we have refrained from providing the
access for this command to userspace.

My approach is, when userspace issues a command we check if command
requires INIT state, if so then we do INIT -> CMD -> SHUTDOWN. If
command can be executed in any state then we issue the command . If
command need to be executed in UINIT state then we *do not* do
SHUTDOWN->CMD, instead we issue the cmd and PSP will fail with error
code and caller can check the error code to determine why command failed.

If I go with your recommendation then I am not able to see who will
transition the platform from UINIT -> INIT so that command can run?

Lets try with very simple example:

# modprobe ccp

/* FW is in INIT state */

# userspace runs

ioctl(fd, SEV_USER_PEK_GEN, &err)

This will fail because PEK_GEN require the platform in INIT state and
nobody has done the state transition from INIT -> UINIT.


-Brijesh
Borislav Petkov Oct. 27, 2017, 9:49 p.m. UTC | #15
On Fri, Oct 27, 2017 at 04:28:31PM -0500, Brijesh Singh wrote:
> This will fail because PEK_GEN require the platform in INIT state and
> nobody has done the state transition from INIT -> UINIT.

Huh, FW is in INIT state and PEK_GEN wants it to be in INIT state. Typo?

Aaanyway, I don't like this whole notion of prematurely and predictively
executing commands on the PSP if it is not needed. So how about
executing only those commands which put the FW in the required state and
then executing the actual command?

I.e., if a command needs to be executed in UINIT state, you put the PSP
in that state before executing that command. If the command needs to be
in INIT state, you put the PSP in INIT state first and so on...

For convenience, you could carry the current PSP state in some struct
psp_dev member or whatever and query it before running the respective
commands.

Hmmm?
Brijesh Singh Oct. 27, 2017, 10:59 p.m. UTC | #16
On 10/27/17 4:49 PM, Borislav Petkov wrote:
> On Fri, Oct 27, 2017 at 04:28:31PM -0500, Brijesh Singh wrote:
>> This will fail because PEK_GEN require the platform in INIT state and
>> nobody has done the state transition from INIT -> UINIT.
> Huh, FW is in INIT state and PEK_GEN wants it to be in INIT state. Typo?

Yes it is typo. PEK_GEN wants FW to be in INIT state hence someone need
to transition from UNINIT -> INIT.

> Aaanyway, I don't like this whole notion of prematurely and predictively
> executing commands on the PSP if it is not needed. So how about
> executing only those commands which put the FW in the required state and
> then executing the actual command?

That's what I am doing except FACTORY_RESET. The FACTORY_RESET require
the FW to be in UNINIT state,  since we don't who else is running in
parallel hence its not safe to issue the SHUTDOWN to transition from
INIT -> UNINIT. If FW is not in correct state this  command will fail
with error code (INVALID_STATE) and user can retry (please note that
user can always use PLATFORM_STATUS to query the current FW state before
issuing a command). I see that we can do a small optimization -- since
we already know the FW state hence we can avoid issuing PSP command when
we know for sure that command will fail because we are not in correct state.

>
> I.e., if a command needs to be executed in UINIT state, you put the PSP
> in that state before executing that command. If the command needs to be
> in INIT state, you put the PSP in INIT state first and so on...

If command needs INIT state and FW is not in INIT state then its safe to
transition from UNINIT -> INIT. But if command needs UNINIT state and FW
is in INIT state then its not safe to transition -- in those case we
simply return EBUSY and let the user retry the command.
Borislav Petkov Oct. 28, 2017, midnight UTC | #17
On Fri, Oct 27, 2017 at 05:59:23PM -0500, Brijesh Singh wrote:
> Yes it is typo. PEK_GEN wants FW to be in INIT state hence someone need
> to transition from UNINIT -> INIT.

Which, once you've done it once on driver init, is there.

> That's what I am doing except FACTORY_RESET.

Well, not really. Lemme pick a command at random...

PEK_CSR. For that, you do INIT -> PEK_CSR -> SHUTDOWN.

Doc says, platform needs to be in INIT or WORKING state. But nothing
says you should shut it down. Spec says, SHUTDOWN transitions platform
to UNINIT state. So when the next command comes in which needs the
platform to be in INIT state, you go and INIT it again. For no reason
*WHATSOEVER*!

I know, you're gonna say, but what if the next command needs a different
state than INIT. Well, *then* you transition it, in the command
function. When that function executes. But not before that and not in
preparation that *maybe* the next command will be it.

Now, if you did:

INIT once during driver init

PEK_CSR

(platform remains in INIT state)

<--- the next command here can execute directly if it is allowed in INIT
state.

Instead, the platform has been shutdown and you init it again. Do you
see now what I mean?

IOW, once you init the PSP master, you should keep it in the INIT state
- or the state in which most commands expect it to be and thus save
yourself all that unnecessary toggling. If a command needs it to be in a
different state, only *then* you transition it.

Instead, what you have now is that you call INIT and SHUTDOWN
around SEV_PEK_GEN, SEV_PDH_GEN, SEV_PEK_CSR, SEV_PEK_CERT_IMPORT,
SEV_PDH_CERT_EXPORT and for all those, the platform must be in INIT
(for some in WORKING state) but for all in INIT state and "The platform
remains be in the same state after completion." So the whole SHUTDOWN ->
INIT wankery in-between is a pure waste of electrons.

>  I see that we can do a small optimization -- since we already know
> the FW state hence we can avoid issuing PSP command when we know for
> sure that command will fail because we are not in correct state.

As I said before, you should do that regardless by recording the current
state of the PSP in variable so that you can save yourself the status
querying.

> If command needs INIT state and FW is not in INIT state then its safe to
> transition from UNINIT -> INIT. But if command needs UNINIT state and FW
> is in INIT state then its not safe to transition -- in those case we
> simply return EBUSY and let the user retry the command.

Whatever - that doesn't contradict what I'm proposing.
Brijesh Singh Oct. 28, 2017, 12:20 p.m. UTC | #18
On 10/27/17 7:00 PM, Borislav Petkov wrote:
> On Fri, Oct 27, 2017 at 05:59:23PM -0500, Brijesh Singh wrote:
>> Yes it is typo. PEK_GEN wants FW to be in INIT state hence someone need
>> to transition from UNINIT -> INIT.
> Which, once you've done it once on driver init, is there.
>
>> That's what I am doing except FACTORY_RESET.
> Well, not really. Lemme pick a command at random...
>
> PEK_CSR. For that, you do INIT -> PEK_CSR -> SHUTDOWN.
>
> Doc says, platform needs to be in INIT or WORKING state. But nothing
> says you should shut it down. Spec says, SHUTDOWN transitions platform
> to UNINIT state. So when the next command comes in which needs the
> platform to be in INIT state, you go and INIT it again. For no reason
> *WHATSOEVER*!
>
> I know, you're gonna say, but what if the next command needs a different
> state than INIT. Well, *then* you transition it, in the command
> function. When that function executes. But not before that and not in
> preparation that *maybe* the next command will be it.
>
> Now, if you did:
>
> INIT once during driver init
>
> PEK_CSR
>
> (platform remains in INIT state)
>
> <--- the next command here can execute directly if it is allowed in INIT
> state.
>
> Instead, the platform has been shutdown and you init it again. Do you
> see now what I mean?

Yes, I can see that with your proposal we may able to save some PSP
interaction because after command execution we do not restore to the
previous state. e.g before executing the PEK_CSR command if FW was in
UINIT then we do UNINIT -> INIT and leave it to INIT state.

> IOW, once you init the PSP master, you should keep it in the INIT state
> - or the state in which most commands expect it to be and thus save
> yourself all that unnecessary toggling. If a command needs it to be in a
> different state, only *then* you transition it.

Let me implement it and send you the patch. I think the command
execution function will look like this:

static int sev_ioctl_do_pek_csr(...)
{
   ....
   ....
   mutex(&fw_init_mutex);

   /* If FW is not in INIT state then initialize before executing command */
   if (psp->sev_state != SEV_STATE_INIT) {
       rc = sev_platform_init(...);
       if (rc) {
         mutex_unlock(&fw_init_mutex);
         return rc;
       }
    }
   
     rc = sev_do_cmd(....)

     mutex_unlock(&fw_init_mutex);

     return rc;
}

and factory reset will look like this

static int sev_ioctl_do_reset(...)
{
   mutex(&fw_init_mutex);

   /* If FW is not in UINIT state then shutdown before executing command */
   if (psp->sev_state != SEV_STATE_INIT)
       sev_platform_shutdown(...);
  
    rc = sev_do_cmd(....)

    mutex_unlock(&fw_init_mutex);

    return rc;
}

> Instead, what you have now is that you call INIT and SHUTDOWN
> around SEV_PEK_GEN, SEV_PDH_GEN, SEV_PEK_CSR, SEV_PEK_CERT_IMPORT,
> SEV_PDH_CERT_EXPORT and for all those, the platform must be in INIT
> (for some in WORKING state) but for all in INIT state and "The platform
> remains be in the same state after completion." So the whole SHUTDOWN ->
> INIT wankery in-between is a pure waste of electrons.
>
>>  I see that we can do a small optimization -- since we already know
>> the FW state hence we can avoid issuing PSP command when we know for
>> sure that command will fail because we are not in correct state.
> As I said before, you should do that regardless by recording the current
> state of the PSP in variable so that you can save yourself the status
> querying.
>
>> If command needs INIT state and FW is not in INIT state then its safe to
>> transition from UNINIT -> INIT. But if command needs UNINIT state and FW
>> is in INIT state then its not safe to transition -- in those case we
>> simply return EBUSY and let the user retry the command.
> Whatever - that doesn't contradict what I'm proposing.
>
diff mbox

Patch

diff --git a/drivers/crypto/ccp/psp-dev.c b/drivers/crypto/ccp/psp-dev.c
index b5789f878560..e9966d5fc6c4 100644
--- a/drivers/crypto/ccp/psp-dev.c
+++ b/drivers/crypto/ccp/psp-dev.c
@@ -26,6 +26,14 @@ 
 #include "sp-dev.h"
 #include "psp-dev.h"
 
+#define DEVICE_NAME	"sev"
+
+static DEFINE_MUTEX(sev_cmd_mutex);
+static DEFINE_MUTEX(fw_init_mutex);
+
+static struct sev_misc_dev *sev_misc_dev;
+static int fw_init_count;
+
 static struct psp_device *psp_alloc_struct(struct sp_device *sp)
 {
 	struct device *dev = sp->dev;
@@ -45,9 +53,298 @@  static struct psp_device *psp_alloc_struct(struct sp_device *sp)
 
 static irqreturn_t psp_irq_handler(int irq, void *data)
 {
+	struct psp_device *psp = data;
+	unsigned int status;
+	int reg;
+
+	/* Read the interrupt status: */
+	status = ioread32(psp->io_regs + PSP_P2CMSG_INTSTS);
+
+	/* Check if it is command completion: */
+	if (!(status & BIT(PSP_CMD_COMPLETE_REG)))
+		goto done;
+
+	/* Check if it is SEV command completion: */
+	reg = ioread32(psp->io_regs + PSP_CMDRESP);
+	if (reg & PSP_CMDRESP_RESP) {
+		psp->sev_int_rcvd = 1;
+		wake_up(&psp->sev_int_queue);
+	}
+
+done:
+	/* Clear the interrupt status by writing the same value we read. */
+	iowrite32(status, psp->io_regs + PSP_P2CMSG_INTSTS);
+
 	return IRQ_HANDLED;
 }
 
+static void sev_wait_cmd_ioc(struct psp_device *psp, unsigned int *reg)
+{
+	psp->sev_int_rcvd = 0;
+
+	wait_event(psp->sev_int_queue, psp->sev_int_rcvd);
+	*reg = ioread32(psp->io_regs + PSP_CMDRESP);
+}
+
+static int sev_cmd_buffer_len(int cmd)
+{
+	switch (cmd) {
+	case SEV_CMD_INIT:			return sizeof(struct sev_data_init);
+	case SEV_CMD_PLATFORM_STATUS:		return sizeof(struct sev_user_data_status);
+	case SEV_CMD_PEK_CSR:			return sizeof(struct sev_data_pek_csr);
+	case SEV_CMD_PEK_CERT_IMPORT:		return sizeof(struct sev_data_pek_cert_import);
+	case SEV_CMD_PDH_CERT_EXPORT:		return sizeof(struct sev_data_pdh_cert_export);
+	case SEV_CMD_LAUNCH_START:		return sizeof(struct sev_data_launch_start);
+	case SEV_CMD_LAUNCH_UPDATE_DATA:	return sizeof(struct sev_data_launch_update_data);
+	case SEV_CMD_LAUNCH_UPDATE_VMSA:	return sizeof(struct sev_data_launch_update_vmsa);
+	case SEV_CMD_LAUNCH_FINISH:		return sizeof(struct sev_data_launch_finish);
+	case SEV_CMD_LAUNCH_MEASURE:		return sizeof(struct sev_data_launch_measure);
+	case SEV_CMD_ACTIVATE:			return sizeof(struct sev_data_activate);
+	case SEV_CMD_DEACTIVATE:		return sizeof(struct sev_data_deactivate);
+	case SEV_CMD_DECOMMISSION:		return sizeof(struct sev_data_decommission);
+	case SEV_CMD_GUEST_STATUS:		return sizeof(struct sev_data_guest_status);
+	case SEV_CMD_DBG_DECRYPT:		return sizeof(struct sev_data_dbg);
+	case SEV_CMD_DBG_ENCRYPT:		return sizeof(struct sev_data_dbg);
+	case SEV_CMD_SEND_START:		return sizeof(struct sev_data_send_start);
+	case SEV_CMD_SEND_UPDATE_DATA:		return sizeof(struct sev_data_send_update_data);
+	case SEV_CMD_SEND_UPDATE_VMSA:		return sizeof(struct sev_data_send_update_vmsa);
+	case SEV_CMD_SEND_FINISH:		return sizeof(struct sev_data_send_finish);
+	case SEV_CMD_RECEIVE_START:		return sizeof(struct sev_data_receive_start);
+	case SEV_CMD_RECEIVE_FINISH:		return sizeof(struct sev_data_receive_finish);
+	case SEV_CMD_RECEIVE_UPDATE_DATA:	return sizeof(struct sev_data_receive_update_data);
+	case SEV_CMD_RECEIVE_UPDATE_VMSA:	return sizeof(struct sev_data_receive_update_vmsa);
+	case SEV_CMD_LAUNCH_UPDATE_SECRET:	return sizeof(struct sev_data_launch_secret);
+	default:				return 0;
+	}
+
+	return 0;
+}
+
+static int sev_do_cmd(int cmd, void *data, int *psp_ret)
+{
+	unsigned int phys_lsb, phys_msb;
+	unsigned int reg, ret = 0;
+	struct psp_device *psp;
+	struct sp_device *sp;
+
+	sp = sp_get_psp_master_device();
+	if (!sp)
+		return -ENODEV;
+
+	psp = sp->psp_data;
+	if (!psp)
+		return -ENODEV;
+
+	/* Get the physical address of the command buffer */
+	phys_lsb = data ? lower_32_bits(__psp_pa(data)) : 0;
+	phys_msb = data ? upper_32_bits(__psp_pa(data)) : 0;
+
+	dev_dbg(psp->dev, "sev command id %#x buffer 0x%08x%08x\n",
+		cmd, phys_msb, phys_lsb);
+
+	print_hex_dump_debug("(in):  ", DUMP_PREFIX_OFFSET, 16, 2, data,
+			     sev_cmd_buffer_len(cmd), false);
+
+	/* Only one command at a time... */
+	mutex_lock(&sev_cmd_mutex);
+
+	iowrite32(phys_lsb, psp->io_regs + PSP_CMDBUFF_ADDR_LO);
+	iowrite32(phys_msb, psp->io_regs + PSP_CMDBUFF_ADDR_HI);
+
+	reg = cmd;
+	reg <<= PSP_CMDRESP_CMD_SHIFT;
+	reg |= PSP_CMDRESP_IOC;
+	iowrite32(reg, psp->io_regs + PSP_CMDRESP);
+
+	/* wait for command completion */
+	sev_wait_cmd_ioc(psp, &reg);
+
+	if (psp_ret)
+		*psp_ret = reg & PSP_CMDRESP_ERR_MASK;
+
+	if (reg & PSP_CMDRESP_ERR_MASK) {
+		dev_dbg(psp->dev, "sev command %#x failed (%#010x)\n",
+			cmd, reg & PSP_CMDRESP_ERR_MASK);
+		ret = -EIO;
+	}
+
+	mutex_unlock(&sev_cmd_mutex);
+	print_hex_dump_debug("(out): ", DUMP_PREFIX_OFFSET, 16, 2, data,
+			     sev_cmd_buffer_len(cmd), false);
+	return ret;
+}
+
+static long sev_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
+{
+	return -ENOTTY;
+}
+
+static const struct file_operations sev_fops = {
+	.owner	= THIS_MODULE,
+	.unlocked_ioctl = sev_ioctl,
+};
+
+static int __sev_platform_init(struct sev_data_init *data, int *error)
+{
+	int rc = 0;
+
+	mutex_lock(&fw_init_mutex);
+
+	if (!fw_init_count) {
+		rc = sev_do_cmd(SEV_CMD_INIT, data, error);
+		if (rc)
+			goto unlock;
+	}
+
+	fw_init_count++;
+
+unlock:
+	mutex_unlock(&fw_init_mutex);
+	return rc;
+
+}
+
+int sev_platform_init(struct sev_data_init *data, int *error)
+{
+	struct sev_data_init *input = NULL;
+	int rc;
+
+	if (!data) {
+		input = kzalloc(sizeof(*input), GFP_KERNEL);
+		if (!input)
+			return -ENOMEM;
+
+		data = input;
+	}
+
+	rc = __sev_platform_init(data, error);
+
+	kfree(input);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(sev_platform_init);
+
+int sev_platform_shutdown(int *error)
+{
+	int rc = 0;
+
+	mutex_lock(&fw_init_mutex);
+
+	if (!fw_init_count)
+		goto unlock;
+
+	if (fw_init_count == 1) {
+		rc = sev_do_cmd(SEV_CMD_SHUTDOWN, 0, error);
+		if (rc)
+			goto unlock;
+	}
+
+	fw_init_count--;
+
+unlock:
+	mutex_unlock(&fw_init_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(sev_platform_shutdown);
+
+int sev_platform_status(struct sev_user_data_status *data, int *error)
+{
+	return sev_do_cmd(SEV_CMD_PLATFORM_STATUS, data, error);
+}
+EXPORT_SYMBOL_GPL(sev_platform_status);
+
+int sev_issue_cmd_external_user(struct file *filep, unsigned int cmd,
+				void *data, int *error)
+{
+	if (!filep || filep->f_op != &sev_fops)
+		return -EBADF;
+
+	return sev_do_cmd(cmd, data, error);
+}
+EXPORT_SYMBOL_GPL(sev_issue_cmd_external_user);
+
+int sev_guest_deactivate(struct sev_data_deactivate *data, int *error)
+{
+	return sev_do_cmd(SEV_CMD_DEACTIVATE, data, error);
+}
+EXPORT_SYMBOL_GPL(sev_guest_deactivate);
+
+int sev_guest_activate(struct sev_data_activate *data, int *error)
+{
+	return sev_do_cmd(SEV_CMD_ACTIVATE, data, error);
+}
+EXPORT_SYMBOL_GPL(sev_guest_activate);
+
+int sev_guest_decommission(struct sev_data_decommission *data, int *error)
+{
+	return sev_do_cmd(SEV_CMD_DECOMMISSION, data, error);
+}
+EXPORT_SYMBOL_GPL(sev_guest_decommission);
+
+int sev_guest_df_flush(int *error)
+{
+	return sev_do_cmd(SEV_CMD_DF_FLUSH, 0, error);
+}
+EXPORT_SYMBOL_GPL(sev_guest_df_flush);
+
+static int sev_ops_init(struct psp_device *psp)
+{
+	struct device *dev = psp->dev;
+	int ret;
+
+	/*
+	 * SEV feature support can be detected on multiple devices but the SEV
+	 * FW commands must be issued on the master. During probe, we do not
+	 * know the master hence we create /dev/sev on the first device probe.
+	 * sev_do_cmd() finds the right master device to which to issue the
+	 * command to the firmware.
+	 */
+	if (!sev_misc_dev) {
+		struct miscdevice *misc;
+
+		sev_misc_dev = devm_kzalloc(dev, sizeof(*sev_misc_dev), GFP_KERNEL);
+		if (!sev_misc_dev)
+			return -ENOMEM;
+
+		misc = &sev_misc_dev->misc;
+		misc->minor = MISC_DYNAMIC_MINOR;
+		misc->name = DEVICE_NAME;
+		misc->fops = &sev_fops;
+
+		ret = misc_register(misc);
+		if (ret)
+			return ret;
+
+		kref_init(&sev_misc_dev->refcount);
+	} else {
+		kref_get(&sev_misc_dev->refcount);
+	}
+
+	init_waitqueue_head(&psp->sev_int_queue);
+	psp->sev_misc = sev_misc_dev;
+	dev_info(dev, "registered SEV device\n");
+
+	return 0;
+}
+
+static int sev_init(struct psp_device *psp)
+{
+	/* Check if device supports SEV feature */
+	if (!(ioread32(psp->io_regs + PSP_FEATURE_REG) & 1)) {
+		dev_dbg(psp->dev, "device does not support SEV\n");
+		return 1;
+	}
+
+	return sev_ops_init(psp);
+}
+
+static void sev_exit(struct kref *ref)
+{
+	struct sev_misc_dev *sev_dev = container_of(ref, struct sev_misc_dev, refcount);
+
+	misc_deregister(&sev_dev->misc);
+}
+
 int psp_dev_init(struct sp_device *sp)
 {
 	struct device *dev = sp->dev;
@@ -84,11 +381,17 @@  int psp_dev_init(struct sp_device *sp)
 	if (sp->set_psp_master_device)
 		sp->set_psp_master_device(sp);
 
+	ret = sev_init(psp);
+	if (ret)
+		goto e_irq;
+
 	/* Enable interrupt */
 	iowrite32(-1, psp->io_regs + PSP_P2CMSG_INTEN);
 
 	return 0;
 
+e_irq:
+	sp_free_psp_irq(psp->sp, psp);
 e_err:
 	sp->psp_data = NULL;
 
@@ -101,5 +404,8 @@  void psp_dev_destroy(struct sp_device *sp)
 {
 	struct psp_device *psp = sp->psp_data;
 
+	if (psp->sev_misc)
+		kref_put(&sev_misc_dev->refcount, sev_exit);
+
 	sp_free_psp_irq(sp, psp);
 }
diff --git a/drivers/crypto/ccp/psp-dev.h b/drivers/crypto/ccp/psp-dev.h
index 55b7808367c3..13467c484f6d 100644
--- a/drivers/crypto/ccp/psp-dev.h
+++ b/drivers/crypto/ccp/psp-dev.h
@@ -25,9 +25,21 @@ 
 #include <linux/interrupt.h>
 #include <linux/irqreturn.h>
 #include <linux/dmaengine.h>
+#include <linux/psp-sev.h>
+#include <linux/miscdevice.h>
 
 #include "sp-dev.h"
 
+#define PSP_C2PMSG(_num)		((_num) << 2)
+#define PSP_CMDRESP			PSP_C2PMSG(32)
+#define PSP_CMDBUFF_ADDR_LO		PSP_C2PMSG(56)
+#define PSP_CMDBUFF_ADDR_HI             PSP_C2PMSG(57)
+#define PSP_FEATURE_REG			PSP_C2PMSG(63)
+
+#define PSP_P2CMSG(_num)		((_num) << 2)
+#define PSP_CMD_COMPLETE_REG		1
+#define PSP_CMD_COMPLETE		PSP_P2CMSG(PSP_CMD_COMPLETE_REG)
+
 #define PSP_P2CMSG_INTEN		0x0110
 #define PSP_P2CMSG_INTSTS		0x0114
 
@@ -44,6 +56,11 @@ 
 
 #define MAX_PSP_NAME_LEN		16
 
+struct sev_misc_dev {
+	struct kref refcount;
+	struct miscdevice misc;
+};
+
 struct psp_device {
 	struct list_head entry;
 
@@ -54,6 +71,10 @@  struct psp_device {
 	struct sp_device *sp;
 
 	void __iomem *io_regs;
+
+	unsigned int sev_int_rcvd;
+	wait_queue_head_t sev_int_queue;
+	struct sev_misc_dev *sev_misc;
 };
 
 #endif /* __PSP_DEV_H */
diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h
index 15bda519538e..21511419bfe6 100644
--- a/include/linux/psp-sev.h
+++ b/include/linux/psp-sev.h
@@ -491,4 +491,163 @@  struct sev_data_dbg {
 	u32 len;				/* In */
 } __packed;
 
+#ifdef CONFIG_CRYPTO_DEV_SP_PSP
+
+/**
+ * sev_platform_init - perform SEV INIT command
+ *
+ * @init: sev_data_init structure to be processed
+ * @error: SEV command return code
+ *
+ * Returns:
+ * 0 if the SEV successfully processed the command
+ * -%ENODEV    if the SEV device is not available
+ * -%ENOTSUPP  if the SEV does not support SEV
+ * -%ETIMEDOUT if the SEV command timed out
+ * -%EIO       if the SEV returned a non-zero return code
+ */
+int sev_platform_init(struct sev_data_init *init, int *error);
+
+/**
+ * sev_platform_shutdown - perform SEV SHUTDOWN command
+ *
+ * @error: SEV command return code
+ *
+ * Returns:
+ * 0 if the SEV successfully processed the command
+ * -%ENODEV    if the SEV device is not available
+ * -%ENOTSUPP  if the SEV does not support SEV
+ * -%ETIMEDOUT if the SEV command timed out
+ * -%EIO       if the SEV returned a non-zero return code
+ */
+int sev_platform_shutdown(int *error);
+
+/**
+ * sev_platform_status - perform SEV PLATFORM_STATUS command
+ *
+ * @init: sev_data_status structure to be processed
+ * @error: SEV command return code
+ *
+ * Returns:
+ * 0 if the SEV successfully processed the command
+ * -%ENODEV    if the SEV device is not available
+ * -%ENOTSUPP  if the SEV does not support SEV
+ * -%ETIMEDOUT if the SEV command timed out
+ * -%EIO       if the SEV returned a non-zero return code
+ */
+int sev_platform_status(struct sev_user_data_status *status, int *error);
+
+/**
+ * sev_issue_cmd_external_user - issue SEV command by other driver with a file
+ * handle.
+ *
+ * This function can be used by other drivers to issue a SEV command on
+ * behalf of userspace. The caller must pass a valid SEV file descriptor
+ * so that we know that it has access to SEV device.
+ *
+ * @filep - SEV device file pointer
+ * @cmd - command to issue
+ * @data - command buffer
+ * @error: SEV command return code
+ *
+ * Returns:
+ * 0 if the SEV successfully processed the command
+ * -%ENODEV    if the SEV device is not available
+ * -%ENOTSUPP  if the SEV does not support SEV
+ * -%ETIMEDOUT if the SEV command timed out
+ * -%EIO       if the SEV returned a non-zero return code
+ * -%EINVAL    if the SEV file descriptor is not valid
+ */
+int sev_issue_cmd_external_user(struct file *filep, unsigned int id,
+				void *data, int *error);
+
+/**
+ * sev_guest_deactivate - perform SEV DEACTIVATE command
+ *
+ * @deactivate: sev_data_deactivate structure to be processed
+ * @sev_ret: sev command return code
+ *
+ * Returns:
+ * 0 if the sev successfully processed the command
+ * -%ENODEV    if the sev device is not available
+ * -%ENOTSUPP  if the sev does not support SEV
+ * -%ETIMEDOUT if the sev command timed out
+ * -%EIO       if the sev returned a non-zero return code
+ */
+int sev_guest_deactivate(struct sev_data_deactivate *data, int *error);
+
+/**
+ * sev_guest_activate - perform SEV ACTIVATE command
+ *
+ * @activate: sev_data_activate structure to be processed
+ * @sev_ret: sev command return code
+ *
+ * Returns:
+ * 0 if the sev successfully processed the command
+ * -%ENODEV    if the sev device is not available
+ * -%ENOTSUPP  if the sev does not support SEV
+ * -%ETIMEDOUT if the sev command timed out
+ * -%EIO       if the sev returned a non-zero return code
+ */
+int sev_guest_activate(struct sev_data_activate *data, int *error);
+
+/**
+ * sev_guest_df_flush - perform SEV DF_FLUSH command
+ *
+ * @sev_ret: sev command return code
+ *
+ * Returns:
+ * 0 if the sev successfully processed the command
+ * -%ENODEV    if the sev device is not available
+ * -%ENOTSUPP  if the sev does not support SEV
+ * -%ETIMEDOUT if the sev command timed out
+ * -%EIO       if the sev returned a non-zero return code
+ */
+int sev_guest_df_flush(int *error);
+
+/**
+ * sev_guest_decommission - perform SEV DECOMMISSION command
+ *
+ * @decommission: sev_data_decommission structure to be processed
+ * @sev_ret: sev command return code
+ *
+ * Returns:
+ * 0 if the sev successfully processed the command
+ * -%ENODEV    if the sev device is not available
+ * -%ENOTSUPP  if the sev does not support SEV
+ * -%ETIMEDOUT if the sev command timed out
+ * -%EIO       if the sev returned a non-zero return code
+ */
+int sev_guest_decommission(struct sev_data_decommission *data, int *error);
+
+#else	/* !CONFIG_CRYPTO_DEV_SP_PSP */
+
+static inline int
+sev_platform_status(struct sev_user_data_status *status, int *error) { return -ENODEV; }
+
+static inline int
+sev_platform_init(struct sev_data_init *init, int *error) { return -ENODEV; }
+
+static inline int sev_platform_shutdown(int *error) { return -ENODEV; }
+
+static inline int
+sev_guest_deactivate(struct sev_data_deactivate *data, int *error) { return -ENODEV; }
+
+static inline int
+sev_guest_decommission(struct sev_data_decommission *data, int *error) { return -ENODEV; }
+
+static inline int
+sev_guest_activate(struct sev_data_activate *data, int *error) { return -ENODEV; }
+
+static inline int sev_guest_df_flush(int *error) { return -ENODEV; }
+
+static inline int
+sev_issue_cmd_external_user(struct file *filep,
+			    unsigned int id, void *data, int *error)
+{
+	return -ENODEV;
+}
+
+#endif	/* CONFIG_CRYPTO_DEV_SP_PSP */
+
 #endif	/* __PSP_SEV_H__ */