diff mbox series

[3/4] firmware: Add support for Qualcomm UEFI Secure Application

Message ID 20220723224949.1089973-4-luzmaximilian@gmail.com
State Superseded
Headers show
Series firmware: Add support for Qualcomm UEFI Secure Application | expand

Commit Message

Maximilian Luz July 23, 2022, 10:49 p.m. UTC
On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
EFI variables cannot be accessed via the standard interface in EFI
runtime mode. The respective functions return EFI_UNSUPPORTED. On these
platforms, we instead need to talk to uefisecapp. This commit provides
support for this and registers the respective efivars operations to
access EFI variables from the kernel.

Communication with uefisecapp follows the standard Qualcomm Trusted
Environment (TEE or TrEE) / Secure OS conventions via the respective SCM
call interface. This is also the reason why variable access works
normally while boot services are active. During this time, said SCM
interface is managed by the boot services. When calling
ExitBootServices(), the ownership is transferred to the kernel.
Therefore, UEFI must not use that interface itself (as multiple parties
accessing this interface at the same time may lead to complications) and
cannot access variables for us.

Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
---
 MAINTAINERS                            |   6 +
 drivers/firmware/Kconfig               |  16 +
 drivers/firmware/Makefile              |   1 +
 drivers/firmware/qcom_tee_uefisecapp.c | 761 +++++++++++++++++++++++++
 4 files changed, 784 insertions(+)
 create mode 100644 drivers/firmware/qcom_tee_uefisecapp.c

Comments

Johan Hovold Jan. 17, 2023, 8:24 a.m. UTC | #1
On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:
> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
> EFI variables cannot be accessed via the standard interface in EFI
> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
> platforms, we instead need to talk to uefisecapp. This commit provides
> support for this and registers the respective efivars operations to
> access EFI variables from the kernel.
> 
> Communication with uefisecapp follows the standard Qualcomm Trusted
> Environment (TEE or TrEE) / Secure OS conventions via the respective SCM
> call interface. This is also the reason why variable access works
> normally while boot services are active. During this time, said SCM
> interface is managed by the boot services. When calling
> ExitBootServices(), the ownership is transferred to the kernel.
> Therefore, UEFI must not use that interface itself (as multiple parties
> accessing this interface at the same time may lead to complications) and
> cannot access variables for us.
> 
> Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
> ---

> +static struct platform_driver qcom_uefisecapp_driver = {
> +	.probe = qcom_uefisecapp_probe,
> +	.remove = qcom_uefisecapp_remove,
> +	.driver = {
> +		.name = "qcom_tee_uefisecapp",
> +		.of_match_table = qcom_uefisecapp_dt_match,
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +};
> +module_platform_driver(qcom_uefisecapp_driver);

I noticed that for efivarfs to work, you're currently relying on having
the firmware still claim that the variable services are supported in the
RT_PROP table so that efi core registers the default ops at subsys init
time (which are later overridden by this driver).

Otherwise efivarfs may fail to initialise when built in:

	static __init int efivarfs_init(void)
	{
		if (!efivars_kobject())
			return -ENODEV;

		return register_filesystem(&efivarfs_type);
	}

	module_init(efivarfs_init);

With recent X13s firmware the corresponding bit in the RT_PROP table has
been cleared so that efivarfs would fail to initialise. Similar problem
when booting with 'efi=noruntime'.

One way to handle this is to register also the qcom_uefisecapp_driver at
subsys init time and prevent it from being built as a module (e.g. as is
done for the SCM driver). I'm using the below patch for this currently.

I guess the Google GSMI implementation suffers from a similar problem.

Johan
Maximilian Luz Jan. 17, 2023, 8:42 a.m. UTC | #2
On 1/17/23 09:24, Johan Hovold wrote:
> On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:
>> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
>> EFI variables cannot be accessed via the standard interface in EFI
>> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
>> platforms, we instead need to talk to uefisecapp. This commit provides
>> support for this and registers the respective efivars operations to
>> access EFI variables from the kernel.
>>
>> Communication with uefisecapp follows the standard Qualcomm Trusted
>> Environment (TEE or TrEE) / Secure OS conventions via the respective SCM
>> call interface. This is also the reason why variable access works
>> normally while boot services are active. During this time, said SCM
>> interface is managed by the boot services. When calling
>> ExitBootServices(), the ownership is transferred to the kernel.
>> Therefore, UEFI must not use that interface itself (as multiple parties
>> accessing this interface at the same time may lead to complications) and
>> cannot access variables for us.
>>
>> Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
>> ---
> 
>> +static struct platform_driver qcom_uefisecapp_driver = {
>> +	.probe = qcom_uefisecapp_probe,
>> +	.remove = qcom_uefisecapp_remove,
>> +	.driver = {
>> +		.name = "qcom_tee_uefisecapp",
>> +		.of_match_table = qcom_uefisecapp_dt_match,
>> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
>> +	},
>> +};
>> +module_platform_driver(qcom_uefisecapp_driver);
> 
> I noticed that for efivarfs to work, you're currently relying on having
> the firmware still claim that the variable services are supported in the
> RT_PROP table so that efi core registers the default ops at subsys init
> time (which are later overridden by this driver).
> 
> Otherwise efivarfs may fail to initialise when built in:
> 
> 	static __init int efivarfs_init(void)
> 	{
> 		if (!efivars_kobject())
> 			return -ENODEV;
> 
> 		return register_filesystem(&efivarfs_type);
> 	}
> 
> 	module_init(efivarfs_init);
> 
> With recent X13s firmware the corresponding bit in the RT_PROP table has
> been cleared so that efivarfs would fail to initialise. Similar problem
> when booting with 'efi=noruntime'.
> 
> One way to handle this is to register also the qcom_uefisecapp_driver at
> subsys init time and prevent it from being built as a module (e.g. as is
> done for the SCM driver). I'm using the below patch for this currently.
> 
> I guess the Google GSMI implementation suffers from a similar problem.

Oh right, thanks for that tip!

I'll try to include that in v2 then. I'll also try to test that case
specifically.

Regards,
Max

>  From 8fecce12d215bd8cab1b8c8f9f0d1e1fe20fe6e7 Mon Sep 17 00:00:00 2001
> From: Johan Hovold <johan+linaro@kernel.org>
> Date: Sun, 15 Jan 2023 15:32:34 +0100
> Subject: [PATCH] firmware: qcom_tee_uefisecapp: register at subsys init
> 
> Register efivars at subsys init time so that it is available when
> efivarfs probes. For the same reason, also prevent building the driver
> as a module.
> 
> This is specifically needed on platforms such as the Lenovo Thinkpad
> X13s where the firmware has cleared the variable services in the RT_PROP
> table so that efi core does not register any efivar callbacks at subsys
> init time (which are later overridden).
> 
> Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
> ---
>   drivers/firmware/Kconfig               | 2 +-
>   drivers/firmware/qcom_tee_uefisecapp.c | 7 ++++++-
>   2 files changed, 7 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index 4e9e2c227899..48e712e363da 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -231,7 +231,7 @@ config QCOM_TEE
>   	select QCOM_SCM
>   
>   config QCOM_TEE_UEFISECAPP
> -	tristate "Qualcomm TrEE UEFI Secure App client driver"
> +	bool "Qualcomm TrEE UEFI Secure App client driver"
>   	select QCOM_TEE
>   	depends on EFI
>   	help
> diff --git a/drivers/firmware/qcom_tee_uefisecapp.c b/drivers/firmware/qcom_tee_uefisecapp.c
> index 65573e4b815a..e83bce4da70a 100644
> --- a/drivers/firmware/qcom_tee_uefisecapp.c
> +++ b/drivers/firmware/qcom_tee_uefisecapp.c
> @@ -754,7 +754,12 @@ static struct platform_driver qcom_uefisecapp_driver = {
>   		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
>   	},
>   };
> -module_platform_driver(qcom_uefisecapp_driver);
> +
> +static int __init qcom_uefisecapp_init(void)
> +{
> +	return platform_driver_register(&qcom_uefisecapp_driver);
> +}
> +subsys_initcall(qcom_uefisecapp_init);
>   
>   MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
>   MODULE_DESCRIPTION("Client driver for Qualcomm TrEE/TZ UEFI Secure App");
Johan Hovold Jan. 17, 2023, 11:05 a.m. UTC | #3
On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:
> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
> EFI variables cannot be accessed via the standard interface in EFI
> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
> platforms, we instead need to talk to uefisecapp. This commit provides
> support for this and registers the respective efivars operations to
> access EFI variables from the kernel.
> 
> Communication with uefisecapp follows the standard Qualcomm Trusted
> Environment (TEE or TrEE) / Secure OS conventions via the respective SCM
> call interface. This is also the reason why variable access works
> normally while boot services are active. During this time, said SCM
> interface is managed by the boot services. When calling
> ExitBootServices(), the ownership is transferred to the kernel.
> Therefore, UEFI must not use that interface itself (as multiple parties
> accessing this interface at the same time may lead to complications) and
> cannot access variables for us.
> 
> Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
> ---

> +static int qcom_uefisecapp_probe(struct platform_device *pdev)
> +{
> +	struct qcuefi_client *qcuefi;
> +	int status;

[...]

> +	/* Set up kobject for efivars interface. */
> +	qcuefi->kobj = kobject_create_and_add("qcom_tee_uefisecapp", firmware_kobj);
> +	if (!qcuefi->kobj) {
> +		status = -ENOMEM;
> +		goto err_kobj;

When preparing some related EFI patches, I noticed that the error labels
here are named after where you jump from rather than after what they do
(as is suggested by the coding standard).

Would you mind changing that (throughout) for your v2?

> +	}
> +
> +	/* Register global reference. */
> +	platform_set_drvdata(pdev, qcuefi);
> +	status = qcuefi_set_reference(qcuefi);
> +	if (status)
> +		goto err_ref;
> +
> +	/* Register efivar ops. */
> +	status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops, qcuefi->kobj);
> +	if (status)
> +		goto err_register;
> +
> +	return 0;
> +
> +err_register:
> +	qcuefi_set_reference(NULL);
> +err_ref:
> +	kobject_put(qcuefi->kobj);
> +err_kobj:
> +	qctee_dma_free(qcuefi->dev, &qcuefi->dma);
> +	return status;
> +}

Johan
Maximilian Luz Jan. 17, 2023, 12:07 p.m. UTC | #4
On 1/17/23 12:05, Johan Hovold wrote:
> On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:
>> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
>> EFI variables cannot be accessed via the standard interface in EFI
>> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
>> platforms, we instead need to talk to uefisecapp. This commit provides
>> support for this and registers the respective efivars operations to
>> access EFI variables from the kernel.
>>
>> Communication with uefisecapp follows the standard Qualcomm Trusted
>> Environment (TEE or TrEE) / Secure OS conventions via the respective SCM
>> call interface. This is also the reason why variable access works
>> normally while boot services are active. During this time, said SCM
>> interface is managed by the boot services. When calling
>> ExitBootServices(), the ownership is transferred to the kernel.
>> Therefore, UEFI must not use that interface itself (as multiple parties
>> accessing this interface at the same time may lead to complications) and
>> cannot access variables for us.
>>
>> Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
>> ---
> 
>> +static int qcom_uefisecapp_probe(struct platform_device *pdev)
>> +{
>> +	struct qcuefi_client *qcuefi;
>> +	int status;
> 
> [...]
> 
>> +	/* Set up kobject for efivars interface. */
>> +	qcuefi->kobj = kobject_create_and_add("qcom_tee_uefisecapp", firmware_kobj);
>> +	if (!qcuefi->kobj) {
>> +		status = -ENOMEM;
>> +		goto err_kobj;
> 
> When preparing some related EFI patches, I noticed that the error labels
> here are named after where you jump from rather than after what they do
> (as is suggested by the coding standard).
> 
> Would you mind changing that (throughout) for your v2?

Not at all. Will change that for v2.

Regards,
Max

>> +	}
>> +
>> +	/* Register global reference. */
>> +	platform_set_drvdata(pdev, qcuefi);
>> +	status = qcuefi_set_reference(qcuefi);
>> +	if (status)
>> +		goto err_ref;
>> +
>> +	/* Register efivar ops. */
>> +	status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops, qcuefi->kobj);
>> +	if (status)
>> +		goto err_register;
>> +
>> +	return 0;
>> +
>> +err_register:
>> +	qcuefi_set_reference(NULL);
>> +err_ref:
>> +	kobject_put(qcuefi->kobj);
>> +err_kobj:
>> +	qctee_dma_free(qcuefi->dev, &qcuefi->dma);
>> +	return status;
>> +}
> 
> Johan
Maximilian Luz Jan. 18, 2023, 8:45 p.m. UTC | #5
On 1/17/23 09:24, Johan Hovold wrote:
> On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:
>> On platforms using the Qualcomm UEFI Secure Application (uefisecapp),
>> EFI variables cannot be accessed via the standard interface in EFI
>> runtime mode. The respective functions return EFI_UNSUPPORTED. On these
>> platforms, we instead need to talk to uefisecapp. This commit provides
>> support for this and registers the respective efivars operations to
>> access EFI variables from the kernel.
>>
>> Communication with uefisecapp follows the standard Qualcomm Trusted
>> Environment (TEE or TrEE) / Secure OS conventions via the respective SCM
>> call interface. This is also the reason why variable access works
>> normally while boot services are active. During this time, said SCM
>> interface is managed by the boot services. When calling
>> ExitBootServices(), the ownership is transferred to the kernel.
>> Therefore, UEFI must not use that interface itself (as multiple parties
>> accessing this interface at the same time may lead to complications) and
>> cannot access variables for us.
>>
>> Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
>> ---
> 
>> +static struct platform_driver qcom_uefisecapp_driver = {
>> +	.probe = qcom_uefisecapp_probe,
>> +	.remove = qcom_uefisecapp_remove,
>> +	.driver = {
>> +		.name = "qcom_tee_uefisecapp",
>> +		.of_match_table = qcom_uefisecapp_dt_match,
>> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
>> +	},
>> +};
>> +module_platform_driver(qcom_uefisecapp_driver);
> 
> I noticed that for efivarfs to work, you're currently relying on having
> the firmware still claim that the variable services are supported in the
> RT_PROP table so that efi core registers the default ops at subsys init
> time (which are later overridden by this driver).
> 
> Otherwise efivarfs may fail to initialise when built in:
> 
> 	static __init int efivarfs_init(void)
> 	{
> 		if (!efivars_kobject())
> 			return -ENODEV;
> 
> 		return register_filesystem(&efivarfs_type);
> 	}
> 
> 	module_init(efivarfs_init);
> 
> With recent X13s firmware the corresponding bit in the RT_PROP table has
> been cleared so that efivarfs would fail to initialise. Similar problem
> when booting with 'efi=noruntime'.
> 
> One way to handle this is to register also the qcom_uefisecapp_driver at
> subsys init time and prevent it from being built as a module (e.g. as is
> done for the SCM driver). I'm using the below patch for this currently.

So I've had another look and I'm not sure this will work reliably:

First, you are correct in case the RT_PROP table is cleared. In that
case, using subsys_initcall() will move the efivar registration before
the efivarfs_init() call.

However, in case EFI indicates support for variables, we will then have
generic_ops_register() and the uefisecapp's driver call running both in
subsys_initcall(). So if I'm not mistaken, this could cause the generic
ops to be registered after the uefisecapp ones, which we want to avoid.

One solution is bumping uefisecapp to fs_initcall(). Or do you have any
other suggestions?

Regards,
Max


>  From 8fecce12d215bd8cab1b8c8f9f0d1e1fe20fe6e7 Mon Sep 17 00:00:00 2001
> From: Johan Hovold <johan+linaro@kernel.org>
> Date: Sun, 15 Jan 2023 15:32:34 +0100
> Subject: [PATCH] firmware: qcom_tee_uefisecapp: register at subsys init
> 
> Register efivars at subsys init time so that it is available when
> efivarfs probes. For the same reason, also prevent building the driver
> as a module.
> 
> This is specifically needed on platforms such as the Lenovo Thinkpad
> X13s where the firmware has cleared the variable services in the RT_PROP
> table so that efi core does not register any efivar callbacks at subsys
> init time (which are later overridden).
> 
> Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
> ---
>   drivers/firmware/Kconfig               | 2 +-
>   drivers/firmware/qcom_tee_uefisecapp.c | 7 ++++++-
>   2 files changed, 7 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index 4e9e2c227899..48e712e363da 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -231,7 +231,7 @@ config QCOM_TEE
>   	select QCOM_SCM
>   
>   config QCOM_TEE_UEFISECAPP
> -	tristate "Qualcomm TrEE UEFI Secure App client driver"
> +	bool "Qualcomm TrEE UEFI Secure App client driver"
>   	select QCOM_TEE
>   	depends on EFI
>   	help
> diff --git a/drivers/firmware/qcom_tee_uefisecapp.c b/drivers/firmware/qcom_tee_uefisecapp.c
> index 65573e4b815a..e83bce4da70a 100644
> --- a/drivers/firmware/qcom_tee_uefisecapp.c
> +++ b/drivers/firmware/qcom_tee_uefisecapp.c
> @@ -754,7 +754,12 @@ static struct platform_driver qcom_uefisecapp_driver = {
>   		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
>   	},
>   };
> -module_platform_driver(qcom_uefisecapp_driver);
> +
> +static int __init qcom_uefisecapp_init(void)
> +{
> +	return platform_driver_register(&qcom_uefisecapp_driver);
> +}
> +subsys_initcall(qcom_uefisecapp_init);
>   
>   MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
>   MODULE_DESCRIPTION("Client driver for Qualcomm TrEE/TZ UEFI Secure App");
Johan Hovold Jan. 19, 2023, 4:47 p.m. UTC | #6
On Wed, Jan 18, 2023 at 09:45:18PM +0100, Maximilian Luz wrote:
> On 1/17/23 09:24, Johan Hovold wrote:
> > On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:

> >> +module_platform_driver(qcom_uefisecapp_driver);
> > 
> > I noticed that for efivarfs to work, you're currently relying on having
> > the firmware still claim that the variable services are supported in the
> > RT_PROP table so that efi core registers the default ops at subsys init
> > time (which are later overridden by this driver).
> > 
> > Otherwise efivarfs may fail to initialise when built in:
> > 
> > 	static __init int efivarfs_init(void)
> > 	{
> > 		if (!efivars_kobject())
> > 			return -ENODEV;
> > 
> > 		return register_filesystem(&efivarfs_type);
> > 	}
> > 
> > 	module_init(efivarfs_init);
> > 
> > With recent X13s firmware the corresponding bit in the RT_PROP table has
> > been cleared so that efivarfs would fail to initialise. Similar problem
> > when booting with 'efi=noruntime'.
> > 
> > One way to handle this is to register also the qcom_uefisecapp_driver at
> > subsys init time and prevent it from being built as a module (e.g. as is
> > done for the SCM driver). I'm using the below patch for this currently.
> 
> So I've had another look and I'm not sure this will work reliably:
> 
> First, you are correct in case the RT_PROP table is cleared. In that
> case, using subsys_initcall() will move the efivar registration before
> the efivarfs_init() call.
> 
> However, in case EFI indicates support for variables, we will then have
> generic_ops_register() and the uefisecapp's driver call running both in
> subsys_initcall(). So if I'm not mistaken, this could cause the generic
> ops to be registered after the uefisecapp ones, which we want to avoid.

Good catch, I was using 'efi=noruntime' on the CRD so I did not notice
that race.

> One solution is bumping uefisecapp to fs_initcall(). Or do you have any
> other suggestions?

I think it would be best to avoid that if we can, but that should work.

The problem here is that the firmware claims to support the EFI variable
services even when it clearly does not and the corresponding callbacks
just return EFI_UNSUPPORTED. As far as I understand, this is still spec
compliant though so we just need to handle that.

One way to address this could be to have efi core not register the
default efivars ops in this case. That would require checking that the
services are indeed available by making one of those calls during
initialisation.

This would however expose the fact that the Google SMI implementation
implicitly relies on overriding the default ops, but I think that's a
good thing as what we have now is racy in multiple ways.

Instead I think we should move the efivarfs availability check from
module init to mount time. That should allow the Google driver, and your
SCM implementation, to continue to be built as modules.

Any consumers (e.g. the Qualcomm RTC driver) would instead need to
check if efivars is available or else defer probe.

Alternatively, it seems all efivars implementation would need to be
always-built in which is not ideal for generic kernels.

I just posted a series here as food for thought:

	https://lore.kernel.org/r/20230119164255.28091-1-johan+linaro@kernel.org

Johan
Maximilian Luz Jan. 19, 2023, 5:19 p.m. UTC | #7
On 1/19/23 17:47, Johan Hovold wrote:
> On Wed, Jan 18, 2023 at 09:45:18PM +0100, Maximilian Luz wrote:
>> On 1/17/23 09:24, Johan Hovold wrote:
>>> On Sun, Jul 24, 2022 at 12:49:48AM +0200, Maximilian Luz wrote:
> 
>>>> +module_platform_driver(qcom_uefisecapp_driver);
>>>
>>> I noticed that for efivarfs to work, you're currently relying on having
>>> the firmware still claim that the variable services are supported in the
>>> RT_PROP table so that efi core registers the default ops at subsys init
>>> time (which are later overridden by this driver).
>>>
>>> Otherwise efivarfs may fail to initialise when built in:
>>>
>>> 	static __init int efivarfs_init(void)
>>> 	{
>>> 		if (!efivars_kobject())
>>> 			return -ENODEV;
>>>
>>> 		return register_filesystem(&efivarfs_type);
>>> 	}
>>>
>>> 	module_init(efivarfs_init);
>>>
>>> With recent X13s firmware the corresponding bit in the RT_PROP table has
>>> been cleared so that efivarfs would fail to initialise. Similar problem
>>> when booting with 'efi=noruntime'.
>>>
>>> One way to handle this is to register also the qcom_uefisecapp_driver at
>>> subsys init time and prevent it from being built as a module (e.g. as is
>>> done for the SCM driver). I'm using the below patch for this currently.
>>
>> So I've had another look and I'm not sure this will work reliably:
>>
>> First, you are correct in case the RT_PROP table is cleared. In that
>> case, using subsys_initcall() will move the efivar registration before
>> the efivarfs_init() call.
>>
>> However, in case EFI indicates support for variables, we will then have
>> generic_ops_register() and the uefisecapp's driver call running both in
>> subsys_initcall(). So if I'm not mistaken, this could cause the generic
>> ops to be registered after the uefisecapp ones, which we want to avoid.
> 
> Good catch, I was using 'efi=noruntime' on the CRD so I did not notice
> that race.
> 
>> One solution is bumping uefisecapp to fs_initcall(). Or do you have any
>> other suggestions?
> 
> I think it would be best to avoid that if we can, but that should work.
> 
> The problem here is that the firmware claims to support the EFI variable
> services even when it clearly does not and the corresponding callbacks
> just return EFI_UNSUPPORTED. As far as I understand, this is still spec
> compliant though so we just need to handle that.
> 
> One way to address this could be to have efi core not register the
> default efivars ops in this case. That would require checking that the
> services are indeed available by making one of those calls during
> initialisation.
> 
> This would however expose the fact that the Google SMI implementation
> implicitly relies on overriding the default ops, but I think that's a
> good thing as what we have now is racy in multiple ways.
> 
> Instead I think we should move the efivarfs availability check from
> module init to mount time. That should allow the Google driver, and your
> SCM implementation, to continue to be built as modules.
> 
> Any consumers (e.g. the Qualcomm RTC driver) would instead need to
> check if efivars is available or else defer probe.
> 
> Alternatively, it seems all efivars implementation would need to be
> always-built in which is not ideal for generic kernels.
> 
> I just posted a series here as food for thought:
> 
> 	https://lore.kernel.org/r/20230119164255.28091-1-johan+linaro@kernel.org

Thanks, I agree that those checks are probably the better option.

Regards,
Max
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index e174747df92f..6e014e16fc82 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16603,6 +16603,12 @@  S:	Maintained
 F:	Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
 F:	drivers/thermal/qcom/
 
+QUALCOMM UEFISECAPP DRIVER
+M:	Maximilian Luz <luzmaximilian@gmail.com>
+L:	linux-arm-msm@vger.kernel.org
+S:	Maintained
+F:	drivers/firmware/qcom_tee_uefisecapp.c
+
 QUALCOMM VENUS VIDEO ACCELERATOR DRIVER
 M:	Stanimir Varbanov <stanimir.varbanov@linaro.org>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index cde60a332b3c..4e9e2c227899 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -230,6 +230,22 @@  config QCOM_TEE
 	tristate
 	select QCOM_SCM
 
+config QCOM_TEE_UEFISECAPP
+	tristate "Qualcomm TrEE UEFI Secure App client driver"
+	select QCOM_TEE
+	depends on EFI
+	help
+	  Various Qualcomm SoCs do not allow direct access to EFI variables.
+	  Instead, these need to be accessed via the UEFI Secure Application
+	  (uefisecapp), residing in the Trusted Execution Environment (TrEE).
+
+	  This module provides a client driver for uefisecapp, installing efivar
+	  operations to allow the kernel accessing EFI variables, and via that also
+	  provide user-space with access to EFI variables to via efivarfs.
+
+	  Select Y or M here to provide access to EFI variables on the
+	  aforementioned platforms.
+
 config SYSFB
 	bool
 	select BOOT_VESA_SUPPORT
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 93dbc6b5a603..3d8e28368b55 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -21,6 +21,7 @@  obj-$(CONFIG_FW_CFG_SYSFS)	+= qemu_fw_cfg.o
 obj-$(CONFIG_QCOM_SCM)		+= qcom-scm.o
 qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
 obj-$(CONFIG_QCOM_TEE)		+= qcom_tee.o
+obj-$(CONFIG_QCOM_TEE_UEFISECAPP) += qcom_tee_uefisecapp.o
 obj-$(CONFIG_SYSFB)		+= sysfb.o
 obj-$(CONFIG_SYSFB_SIMPLEFB)	+= sysfb_simplefb.o
 obj-$(CONFIG_TI_SCI_PROTOCOL)	+= ti_sci.o
diff --git a/drivers/firmware/qcom_tee_uefisecapp.c b/drivers/firmware/qcom_tee_uefisecapp.c
new file mode 100644
index 000000000000..65573e4b815a
--- /dev/null
+++ b/drivers/firmware/qcom_tee_uefisecapp.c
@@ -0,0 +1,761 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Client driver for Qualcomm UEFI Secure Application (qcom.tz.uefisecapp).
+ * Provides access to UEFI variables on platforms where they are secured by the
+ * aforementioned Trusted Execution Environment (TEE) application.
+ *
+ * Copyright (C) 2022 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <linux/efi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/qcom_tee.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+
+/* -- UTF-16 helpers. ------------------------------------------------------- */
+
+static unsigned long utf16_strnlen(const efi_char16_t *str, unsigned long max)
+{
+	size_t i;
+
+	for (i = 0; *str != 0 && i < max; i++, str++) {
+		/* Do nothing, all is handled in the for statement. */
+	}
+
+	return i;
+}
+
+/**
+ * utf16_strsize() - Compute the number of bytes required to store a
+ * null-terminated UTF-16 string.
+ * @str: The string to compute the size for.
+ *
+ * Return: Returns the minimum number of bytes required to store the given
+ * null-terminated string, including its null-terminator.
+ */
+static unsigned long utf16_strsize(const efi_char16_t *str)
+{
+	return (utf16_strnlen(str, U32_MAX) + 1) * sizeof(str[0]);
+}
+
+static unsigned long utf16_strlcpy(efi_char16_t *dst, const efi_char16_t *src, unsigned long size)
+{
+	unsigned long actual = utf16_strnlen(src, size - 1);
+
+	memcpy(dst, src, actual * sizeof(src[0]));
+	dst[actual] = 0;
+
+	return actual;
+}
+
+/**
+ * utf16_copy_to_buf() - Copy the given UTF-16 string to a buffer.
+ * @dst:   Pointer to the output buffer
+ * @src:   Pointer to the null-terminated UTF-16 string to be copied.
+ * @bytes: Maximum number of bytes to copy.
+ *
+ * Copies the given string to the given buffer, ensuring that the output buffer
+ * is not overrun and that the string in the output buffer will always be
+ * null-terminated.
+ *
+ * Return: Returns the length of the copied string, without null-terminator.
+ */
+static unsigned long utf16_copy_to_buf(efi_char16_t *dst, const efi_char16_t *src,
+				       unsigned long bytes)
+{
+	return utf16_strlcpy(dst, src, bytes / sizeof(src[0]));
+}
+
+
+/* -- Qualcomm "uefisecapp" interface definitions. -------------------------- */
+
+#define QCTEE_UEFISEC_APP_NAME			"qcom.tz.uefisecapp"
+
+#define QCTEE_CMD_UEFI(x)			(0x8000 | (x))
+#define QCTEE_CMD_UEFI_GET_VARIABLE		QCTEE_CMD_UEFI(0)
+#define QCTEE_CMD_UEFI_SET_VARIABLE		QCTEE_CMD_UEFI(1)
+#define QCTEE_CMD_UEFI_GET_NEXT_VARIABLE	QCTEE_CMD_UEFI(2)
+#define QCTEE_CMD_UEFI_QUERY_VARIABLE_INFO	QCTEE_CMD_UEFI(3)
+
+/**
+ * struct qctee_req_uefi_get_variable - Request for GetVariable command.
+ * @command_id:  The ID of the command. Must be %QCTEE_CMD_UEFI_GET_VARIABLE.
+ * @length:      Length of the request in bytes, including this struct and any
+ *               parameters (name, GUID) stored after it as well as any padding
+ *               thereof for alignment.
+ * @name_offset: Offset from the start of this struct to where the variable
+ *               name is stored (as utf-16 string), in bytes.
+ * @name_size:   Size of the name parameter in bytes, including null-terminator.
+ * @guid_offset: Offset from the start of this struct to where the GUID
+ *               parameter is stored, in bytes.
+ * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @data_size:   Size of the output buffer, in bytes.
+ */
+struct qctee_req_uefi_get_variable {
+	u32 command_id;
+	u32 length;
+	u32 name_offset;
+	u32 name_size;
+	u32 guid_offset;
+	u32 guid_size;
+	u32 data_size;
+} __packed;
+
+/**
+ * struct qctee_rsp_uefi_get_variable - Response for GetVariable command.
+ * @command_id:  The ID of the command. Should be %QCTEE_CMD_UEFI_GET_VARIABLE.
+ * @length:      Length of the response in bytes, including this struct and the
+ *               returned data.
+ * @status:      Status of this command.
+ * @attributes:  EFI variable attributes.
+ * @data_offset: Offset from the start of this struct to where the data is
+ *               stored, in bytes.
+ * @data_size:   Size of the returned data, in bytes. In case status indicates
+ *               that the buffer is too small, this will be the size required
+ *               to store the EFI variable data.
+ */
+struct qctee_rsp_uefi_get_variable {
+	u32 command_id;
+	u32 length;
+	u32 status;
+	u32 attributes;
+	u32 data_offset;
+	u32 data_size;
+} __packed;
+
+/**
+ * struct qctee_req_uefi_set_variable - Request for the SetVariable command.
+ * @command_id:  The ID of the command. Must be %QCTEE_CMD_UEFI_SET_VARIABLE.
+ * @length:      Length of the request in bytes, including this struct and any
+ *               parameters (name, GUID, data) stored after it as well as any
+ *               padding thereof required for alignment.
+ * @name_offset: Offset from the start of this struct to where the variable
+ *               name is stored (as utf-16 string), in bytes.
+ * @name_size:   Size of the name parameter in bytes, including null-terminator.
+ * @guid_offset: Offset from the start of this struct to where the GUID
+ *               parameter is stored, in bytes.
+ * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @attributes:  The EFI variable attributes to set for this variable.
+ * @data_offset: Offset from the start of this struct to where the EFI variable
+ *               data is stored, in bytes.
+ * @data_size:   Size of EFI variable data, in bytes.
+ *
+ */
+struct qctee_req_uefi_set_variable {
+	u32 command_id;
+	u32 length;
+	u32 name_offset;
+	u32 name_size;
+	u32 guid_offset;
+	u32 guid_size;
+	u32 attributes;
+	u32 data_offset;
+	u32 data_size;
+} __packed;
+
+/**
+ * struct qctee_rsp_uefi_set_variable - Response for the SetVariable command.
+ * @command_id:  The ID of the command. Should be %QCTEE_CMD_UEFI_SET_VARIABLE.
+ * @length:      The length of this response, i.e. the size of this struct in
+ *               bytes.
+ * @status:      Status of this command.
+ * @_unknown1:   Unknown response field.
+ * @_unknown2:   Unknown response field.
+ */
+struct qctee_rsp_uefi_set_variable {
+	u32 command_id;
+	u32 length;
+	u32 status;
+	u32 _unknown1;
+	u32 _unknown2;
+} __packed;
+
+/**
+ * struct qctee_req_uefi_get_next_variable - Request for the
+ * GetNextVariableName command.
+ * @command_id:  The ID of the command. Must be
+ *               %QCTEE_CMD_UEFI_GET_NEXT_VARIABLE.
+ * @length:      Length of the request in bytes, including this struct and any
+ *               parameters (name, GUID) stored after it as well as any padding
+ *               thereof for alignment.
+ * @guid_offset: Offset from the start of this struct to where the GUID
+ *               parameter is stored, in bytes.
+ * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @name_offset: Offset from the start of this struct to where the variable
+ *               name is stored (as utf-16 string), in bytes.
+ * @name_size:   Size of the name parameter in bytes, including null-terminator.
+ */
+struct qctee_req_uefi_get_next_variable {
+	u32 command_id;
+	u32 length;
+	u32 guid_offset;
+	u32 guid_size;
+	u32 name_offset;
+	u32 name_size;
+} __packed;
+
+/**
+ * struct qctee_rsp_uefi_get_next_variable - Response for the
+ * GetNextVariableName command.
+ * @command_id:  The ID of the command. Should be
+ *               %QCTEE_CMD_UEFI_GET_NEXT_VARIABLE.
+ * @length:      Length of the response in bytes, including this struct and any
+ *               parameters (name, GUID) stored after it as well as any padding
+ *               thereof for alignment.
+ * @status:      Status of this command.
+ * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
+ * @name_offset: Offset from the start of this struct to where the variable
+ *               name is stored (as utf-16 string), in bytes.
+ * @name_size:   Size of the name parameter in bytes, including null-terminator.
+ */
+struct qctee_rsp_uefi_get_next_variable {
+	u32 command_id;
+	u32 length;
+	u32 status;
+	u32 guid_offset;
+	u32 guid_size;
+	u32 name_offset;
+	u32 name_size;
+} __packed;
+
+/**
+ * struct qctee_req_uefi_query_variable_info - Response for the
+ * GetNextVariableName command.
+ * @command_id: The ID of the command. Must be
+ *              %QCTEE_CMD_UEFI_QUERY_VARIABLE_INFO.
+ * @length:     The length of this request, i.e. the size of this struct in
+ *              bytes.
+ * @attributes: The storage attributes to query the info for.
+ */
+struct qctee_req_uefi_query_variable_info {
+	u32 command_id;
+	u32 length;
+	u32 attributes;
+} __packed;
+
+/**
+ * struct qctee_rsp_uefi_query_variable_info - Response for the
+ * GetNextVariableName command.
+ * @command_id:        The ID of the command. Must be
+ *                     %QCTEE_CMD_UEFI_QUERY_VARIABLE_INFO.
+ * @length:            The length of this response, i.e. the size of this
+ *                     struct in bytes.
+ * @status:            Status of this command.
+ * @_pad:              Padding.
+ * @storage_space:     Full storage space size, in bytes.
+ * @remaining_space:   Free storage space available, in bytes.
+ * @max_variable_size: Maximum variable data size, in bytes.
+ */
+struct qctee_rsp_uefi_query_variable_info {
+	u32 command_id;
+	u32 length;
+	u32 status;
+	u32 _pad;
+	u64 storage_space;
+	u64 remaining_space;
+	u64 max_variable_size;
+} __packed;
+
+
+/* -- UEFI app interface. --------------------------------------------------- */
+
+struct qcuefi_client {
+	struct device *dev;
+	struct kobject *kobj;
+	struct efivars efivars;
+	struct qctee_dma dma;
+	u32 app_id;
+};
+
+static efi_status_t qctee_uefi_status_to_efi(u32 status)
+{
+	u64 category = status & 0xf0000000;
+	u64 code = status & 0x0fffffff;
+
+	return category << (BITS_PER_LONG - 32) | code;
+}
+
+static efi_status_t qctee_uefi_get_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
+					    const efi_guid_t *guid, u32 *attributes,
+					    unsigned long *data_size, void *data)
+{
+	struct qctee_req_uefi_get_variable *req_data;
+	struct qctee_rsp_uefi_get_variable *rsp_data;
+	struct qctee_dma dma_req;
+	struct qctee_dma dma_rsp;
+	unsigned long name_size = utf16_strsize(name);
+	unsigned long buffer_size = *data_size;
+	unsigned long size;
+	efi_status_t efi_status;
+	int status;
+
+	/* Validation: We need a name and GUID. */
+	if (!name || !guid)
+		return EFI_INVALID_PARAMETER;
+
+	/* Validation: We need a buffer if the buffer_size is nonzero. */
+	if (buffer_size && !data)
+		return EFI_INVALID_PARAMETER;
+
+	/* Compute required size (upper limit with alignments). */
+	size = sizeof(*req_data) + sizeof(*guid) + name_size  /* Inputs. */
+	       + sizeof(*rsp_data) + buffer_size              /* Outputs. */
+	       + 2 * (QCTEE_DMA_ALIGNMENT - 1)                /* Input parameter alignments. */
+	       + 1 * (QCTEE_DMA_ALIGNMENT - 1);               /* Output parameter alignments. */
+
+	/* Make sure we have enough DMA memory. */
+	status = qctee_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
+	if (status)
+		return EFI_OUT_OF_RESOURCES;
+
+	/* Align request struct. */
+	qctee_dma_aligned(&qcuefi->dma, &dma_req, 0);
+	req_data = dma_req.virt;
+
+	/* Set up request data. */
+	req_data->command_id = QCTEE_CMD_UEFI_GET_VARIABLE;
+	req_data->data_size = buffer_size;
+	req_data->name_offset = sizeof(*req_data);
+	req_data->name_size = name_size;
+	req_data->guid_offset = QCTEE_DMA_ALIGN(req_data->name_offset + name_size);
+	req_data->guid_size = sizeof(*guid);
+	req_data->length = req_data->guid_offset + req_data->guid_size;
+
+	dma_req.size = req_data->length;
+
+	/* Copy request parameters. */
+	utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, name_size);
+	memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
+
+	/* Align response struct. */
+	qctee_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
+	rsp_data = dma_rsp.virt;
+
+	/* Perform SCM call. */
+	status = qctee_app_send(qcuefi->dev, qcuefi->app_id, &dma_req, &dma_rsp);
+
+	/* Check for errors and validate. */
+	if (status)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->command_id != QCTEE_CMD_UEFI_GET_VARIABLE)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->status) {
+		dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
+		efi_status = qctee_uefi_status_to_efi(rsp_data->status);
+
+		/* Update size and attributes in case buffer is too small. */
+		if (efi_status == EFI_BUFFER_TOO_SMALL) {
+			*data_size = rsp_data->data_size;
+			if (attributes)
+				*attributes = rsp_data->attributes;
+		}
+
+		return efi_status;
+	}
+
+	if (rsp_data->data_offset + rsp_data->data_size > rsp_data->length)
+		return EFI_DEVICE_ERROR;
+
+	/* Set attributes and data size even if buffer is too small. */
+	*data_size = rsp_data->data_size;
+	if (attributes)
+		*attributes = rsp_data->attributes;
+
+	/*
+	 * If we have a buffer size of zero and no buffer, just return
+	 * attributes and required size.
+	 */
+	if (buffer_size == 0 && !data)
+		return EFI_SUCCESS;
+
+	/* Validate output buffer size. */
+	if (buffer_size < rsp_data->data_size)
+		return EFI_BUFFER_TOO_SMALL;
+
+	/* Copy to output buffer. Note: We're guaranteed to have one at this point. */
+	memcpy(data, dma_rsp.virt + rsp_data->data_offset, rsp_data->data_size);
+	return EFI_SUCCESS;
+}
+
+static efi_status_t qctee_uefi_set_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
+					    const efi_guid_t *guid, u32 attributes,
+					    unsigned long data_size, const void *data)
+{
+	struct qctee_req_uefi_set_variable *req_data;
+	struct qctee_rsp_uefi_set_variable *rsp_data;
+	struct qctee_dma dma_req;
+	struct qctee_dma dma_rsp;
+	unsigned long name_size = utf16_strsize(name);
+	unsigned long size;
+	int status;
+
+	/* Validate inputs. */
+	if (!name || !guid)
+		return EFI_INVALID_PARAMETER;
+
+	/*
+	 * Make sure we have some data if data_size is nonzero. Note: Using a
+	 * size of zero is valid and deletes the variable.
+	 */
+	if (data_size && !data)
+		return EFI_INVALID_PARAMETER;
+
+	/* Compute required size (upper limit with alignments). */
+	size = sizeof(*req_data) + name_size + sizeof(*guid) + data_size  /* Inputs. */
+	       + sizeof(*rsp_data)                            /* Outputs. */
+	       + 2 * (QCTEE_DMA_ALIGNMENT - 1)                /* Input parameter alignments. */
+	       + 1 * (QCTEE_DMA_ALIGNMENT - 1);               /* Output parameter alignments. */
+
+	/* Make sure we have enough DMA memory. */
+	status = qctee_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
+	if (status)
+		return EFI_OUT_OF_RESOURCES;
+
+	/* Align request struct. */
+	qctee_dma_aligned(&qcuefi->dma, &dma_req, 0);
+	req_data = dma_req.virt;
+
+	/* Set up request data. */
+	req_data->command_id = QCTEE_CMD_UEFI_SET_VARIABLE;
+	req_data->attributes = attributes;
+	req_data->name_offset = sizeof(*req_data);
+	req_data->name_size = name_size;
+	req_data->guid_offset = QCTEE_DMA_ALIGN(req_data->name_offset + name_size);
+	req_data->guid_size = sizeof(*guid);
+	req_data->data_offset = req_data->guid_offset + req_data->guid_size;
+	req_data->data_size = data_size;
+	req_data->length = req_data->data_offset + data_size;
+
+	/* Copy request parameters. */
+	utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, req_data->name_size);
+	memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
+
+	if (data_size)
+		memcpy(dma_req.virt + req_data->data_offset, data, req_data->data_size);
+
+	/* Align response struct. */
+	qctee_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
+	rsp_data = dma_rsp.virt;
+
+	/* Perform SCM call. */
+	dma_req.size = req_data->length;
+	dma_rsp.size = sizeof(*rsp_data);
+
+	status = qctee_app_send(qcuefi->dev, qcuefi->app_id, &dma_req, &dma_rsp);
+
+	/* Check for errors and validate. */
+	if (status)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->command_id != QCTEE_CMD_UEFI_SET_VARIABLE)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->status) {
+		dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
+		return qctee_uefi_status_to_efi(rsp_data->status);
+	}
+
+	return EFI_SUCCESS;
+}
+
+static efi_status_t qctee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
+						 unsigned long *name_size, efi_char16_t *name,
+						 efi_guid_t *guid)
+{
+	struct qctee_req_uefi_get_next_variable *req_data;
+	struct qctee_rsp_uefi_get_next_variable *rsp_data;
+	struct qctee_dma dma_req;
+	struct qctee_dma dma_rsp;
+	unsigned long size;
+	efi_status_t efi_status;
+	int status;
+
+	/* We need some buffers. */
+	if (!name_size || !name || !guid)
+		return EFI_INVALID_PARAMETER;
+
+	/* There needs to be at least a single null-character. */
+	if (*name_size == 0)
+		return EFI_INVALID_PARAMETER;
+
+	/* Compute required size (upper limit with alignments). */
+	size = sizeof(*req_data) + sizeof(*guid) + *name_size    /* Inputs. */
+	       + sizeof(*rsp_data) + sizeof(*guid) + *name_size  /* Outputs. */
+	       + 2 * (QCTEE_DMA_ALIGNMENT - 1)                   /* Input parameter alignments. */
+	       + 1 * (QCTEE_DMA_ALIGNMENT - 1);                  /* Output parameter alignments. */
+
+	/* Make sure we have enough DMA memory. */
+	status = qctee_dma_realloc(qcuefi->dev, &qcuefi->dma, size, GFP_KERNEL);
+	if (status)
+		return EFI_OUT_OF_RESOURCES;
+
+	/* Align request struct. */
+	qctee_dma_aligned(&qcuefi->dma, &dma_req, 0);
+	req_data = dma_req.virt;
+
+	/* Set up request data. */
+	req_data->command_id = QCTEE_CMD_UEFI_GET_NEXT_VARIABLE;
+	req_data->guid_offset = QCTEE_DMA_ALIGN(sizeof(*req_data));
+	req_data->guid_size = sizeof(*guid);
+	req_data->name_offset = req_data->guid_offset + req_data->guid_size;
+	req_data->name_size = *name_size;
+	req_data->length = req_data->name_offset + req_data->name_size;
+
+	dma_req.size = req_data->length;
+
+	/* Copy request parameters. */
+	memcpy(dma_req.virt + req_data->guid_offset, guid, req_data->guid_size);
+	utf16_copy_to_buf(dma_req.virt + req_data->name_offset, name, *name_size);
+
+	/* Align response struct. */
+	qctee_dma_aligned(&qcuefi->dma, &dma_rsp, req_data->length);
+	rsp_data = dma_rsp.virt;
+
+	/* Perform SCM call. */
+	status = qctee_app_send(qcuefi->dev, qcuefi->app_id, &dma_req, &dma_rsp);
+
+	/* Check for errors and validate. */
+	if (status)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->command_id != QCTEE_CMD_UEFI_GET_NEXT_VARIABLE)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->length < sizeof(*rsp_data) || rsp_data->length > dma_rsp.size)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->status) {
+		dev_dbg(qcuefi->dev, "%s: uefisecapp error: 0x%x\n", __func__, rsp_data->status);
+		efi_status = qctee_uefi_status_to_efi(rsp_data->status);
+
+		/* Update size with required size in case buffer is too small. */
+		if (efi_status == EFI_BUFFER_TOO_SMALL)
+			*name_size = rsp_data->name_size;
+
+		return efi_status;
+	}
+
+	if (rsp_data->name_offset + rsp_data->name_size > rsp_data->length)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->guid_offset + rsp_data->guid_size > rsp_data->length)
+		return EFI_DEVICE_ERROR;
+
+	if (rsp_data->name_size > *name_size) {
+		*name_size = rsp_data->name_size;
+		return EFI_BUFFER_TOO_SMALL;
+	}
+
+	if (rsp_data->guid_size != sizeof(*guid))
+		return EFI_DEVICE_ERROR;
+
+	/* Copy response fields. */
+	memcpy(guid, dma_rsp.virt + rsp_data->guid_offset, rsp_data->guid_size);
+	utf16_copy_to_buf(name, dma_rsp.virt + rsp_data->name_offset, rsp_data->name_size);
+	*name_size = rsp_data->name_size;
+
+	return 0;
+}
+
+
+/* -- Global efivar interface. ---------------------------------------------- */
+
+static struct qcuefi_client *__qcuefi;
+static DEFINE_MUTEX(__qcuefi_lock);
+
+static int qcuefi_set_reference(struct qcuefi_client *qcuefi)
+{
+	mutex_lock(&__qcuefi_lock);
+
+	if (qcuefi && __qcuefi) {
+		mutex_unlock(&__qcuefi_lock);
+		return -EEXIST;
+	}
+
+	__qcuefi = qcuefi;
+
+	mutex_unlock(&__qcuefi_lock);
+	return 0;
+}
+
+static struct qcuefi_client *qcuefi_acquire(void)
+{
+	mutex_lock(&__qcuefi_lock);
+	return __qcuefi;
+}
+
+static void qcuefi_release(void)
+{
+	mutex_unlock(&__qcuefi_lock);
+}
+
+static efi_status_t qcuefi_get_variable(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
+					unsigned long *data_size, void *data)
+{
+	struct qcuefi_client *qcuefi;
+	efi_status_t status;
+
+	qcuefi = qcuefi_acquire();
+	if (!qcuefi)
+		return EFI_NOT_READY;
+
+	status = qctee_uefi_get_variable(qcuefi, name, vendor, attr, data_size, data);
+
+	qcuefi_release();
+	return status;
+}
+
+static efi_status_t qcuefi_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+					u32 attr, unsigned long data_size, void *data)
+{
+	struct qcuefi_client *qcuefi;
+	efi_status_t status;
+
+	qcuefi = qcuefi_acquire();
+	if (!qcuefi)
+		return EFI_NOT_READY;
+
+	status = qctee_uefi_set_variable(qcuefi, name, vendor, attr, data_size, data);
+
+	qcuefi_release();
+	return status;
+}
+
+static efi_status_t qcuefi_get_next_variable(unsigned long *name_size, efi_char16_t *name,
+					     efi_guid_t *vendor)
+{
+	struct qcuefi_client *qcuefi;
+	efi_status_t status;
+
+	qcuefi = qcuefi_acquire();
+	if (!qcuefi)
+		return EFI_NOT_READY;
+
+	status = qctee_uefi_get_next_variable(qcuefi, name_size, name, vendor);
+
+	qcuefi_release();
+	return status;
+}
+
+static const struct efivar_operations qcom_efivar_ops = {
+	.get_variable = qcuefi_get_variable,
+	.set_variable = qcuefi_set_variable,
+	.get_next_variable = qcuefi_get_next_variable,
+};
+
+
+/* -- Driver setup. --------------------------------------------------------- */
+
+static int qcom_uefisecapp_probe(struct platform_device *pdev)
+{
+	struct qcuefi_client *qcuefi;
+	int status;
+
+	/* Defer until SCM is available. */
+	if (!qcom_scm_is_available())
+		return -EPROBE_DEFER;
+
+	/* Allocate driver data. */
+	qcuefi = devm_kzalloc(&pdev->dev, sizeof(*qcuefi), GFP_KERNEL);
+	if (!qcuefi)
+		return -ENOMEM;
+
+	qcuefi->dev = &pdev->dev;
+
+	/* Get application id for uefisecapp. */
+	status = qctee_app_get_id(&pdev->dev, QCTEE_UEFISEC_APP_NAME, &qcuefi->app_id);
+	if (status) {
+		dev_err(&pdev->dev, "failed to query app ID: %d\n", status);
+		return status;
+	}
+
+	/* Set up DMA. One page should be plenty to start with. */
+	if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) {
+		dev_warn(&pdev->dev, "no suitable DMA available\n");
+		return -EFAULT;
+	}
+
+	status = qctee_dma_alloc(&pdev->dev, &qcuefi->dma, PAGE_SIZE, GFP_KERNEL);
+	if (status)
+		return status;
+
+	/* Set up kobject for efivars interface. */
+	qcuefi->kobj = kobject_create_and_add("qcom_tee_uefisecapp", firmware_kobj);
+	if (!qcuefi->kobj) {
+		status = -ENOMEM;
+		goto err_kobj;
+	}
+
+	/* Register global reference. */
+	platform_set_drvdata(pdev, qcuefi);
+	status = qcuefi_set_reference(qcuefi);
+	if (status)
+		goto err_ref;
+
+	/* Register efivar ops. */
+	status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops, qcuefi->kobj);
+	if (status)
+		goto err_register;
+
+	return 0;
+
+err_register:
+	qcuefi_set_reference(NULL);
+err_ref:
+	kobject_put(qcuefi->kobj);
+err_kobj:
+	qctee_dma_free(qcuefi->dev, &qcuefi->dma);
+	return status;
+}
+
+static int qcom_uefisecapp_remove(struct platform_device *pdev)
+{
+	struct qcuefi_client *qcuefi = platform_get_drvdata(pdev);
+
+	/* Unregister efivar ops. */
+	efivars_unregister(&qcuefi->efivars);
+
+	/* Block on pending calls and unregister global reference. */
+	qcuefi_set_reference(NULL);
+
+	/* Free remaining resources. */
+	kobject_put(qcuefi->kobj);
+	qctee_dma_free(qcuefi->dev, &qcuefi->dma);
+
+	return 0;
+}
+
+static const struct of_device_id qcom_uefisecapp_dt_match[] = {
+	{ .compatible = "qcom,tee-uefisecapp", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, qcom_uefisecapp_dt_match);
+
+static struct platform_driver qcom_uefisecapp_driver = {
+	.probe = qcom_uefisecapp_probe,
+	.remove = qcom_uefisecapp_remove,
+	.driver = {
+		.name = "qcom_tee_uefisecapp",
+		.of_match_table = qcom_uefisecapp_dt_match,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+module_platform_driver(qcom_uefisecapp_driver);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("Client driver for Qualcomm TrEE/TZ UEFI Secure App");
+MODULE_LICENSE("GPL");