diff mbox series

[v5,1/3] efi_loader: Add SPI I/O protocol support

Message ID 20221123175006.4080122-2-paul.barker@sancloud.com
State Changes Requested, archived
Delegated to: Heinrich Schuchardt
Headers show
Series Support UEFI SPI I/O protocol | expand

Commit Message

Paul Barker Nov. 23, 2022, 5:50 p.m. UTC
This addition allows UEFI applications running under u-boot to access
peripherals on SPI busses. It is based on the UEFI Platform
Initialization (PI) Specification, Version 1.7 Errata A (April 2020).
Only the core functionality required to discover SPI peripherals and
communicate with them is currently implemented. Other functionality such
as the legacy SPI controller interface and the ability to update the SPI
peripheral object associated with a particular SPI I/O protocol object
is currently unimplemented.

The following protocols are defined:
* EFI_SPI_CONFIGURATION_PROTOCOL
* EFI_SPI_IO_PROTOCOL
* EFI_LEGACY_SPI_CONTROLLER_PROTOCOL

Since there are no open source implementations of these protocols to use
as an example, educated guesses/hacks have been made in cases where the
UEFI PI specification is unclear and these are documented in comments.

This implementation has been tested on the SanCloud BBE Lite and allowed
a UEFI test application to successfully communicate with a Micron
Authenta flash device connected via the SPI bus.

Signed-off-by: Paul Barker <paul.barker@sancloud.com>
---
 MAINTAINERS                       |   6 +
 configs/am335x_evm_defconfig      |   1 +
 include/efi_api.h                 |   4 +
 include/efi_loader.h              |   4 +
 include/efi_spi_protocol.h        | 166 +++++++++
 lib/efi_loader/Kconfig            |   8 +
 lib/efi_loader/Makefile           |   1 +
 lib/efi_loader/efi_setup.c        |   6 +
 lib/efi_loader/efi_spi_protocol.c | 576 ++++++++++++++++++++++++++++++
 lib/uuid.c                        |   4 +
 10 files changed, 776 insertions(+)
 create mode 100644 include/efi_spi_protocol.h
 create mode 100644 lib/efi_loader/efi_spi_protocol.c

Comments

Ilias Apalodimas Dec. 13, 2022, 7:15 a.m. UTC | #1
Hi Paul,

Apologies for the delayed reply.

[...]

> +static efi_status_t
> +export_spi_peripheral(struct efi_spi_bus *bus, struct udevice *dev)
> +{
> +	efi_string_t name_utf16, vendor_utf16, part_number_utf16;
> +	struct efi_spi_peripheral_priv *priv;
> +	efi_status_t status;
> +	efi_handle_t handle = NULL;
> +	struct udevice *dev_bus = dev->parent;
> +	struct spi_slave *target;
> +	const char *name = dev_read_name(dev);
> +	const char *vendor = dev_read_string(dev, "u-boot,uefi-spi-vendor");
> +	const char *part_number = dev_read_string(dev,
> +			"u-boot,uefi-spi-part-number");
> +	efi_guid_t *guid = (efi_guid_t *)dev_read_u8_array_ptr(dev,
> +			"u-boot,uefi-spi-io-guid", 16);
> +
> +	if (device_get_uclass_id(dev) == UCLASS_SPI_EMUL) {
> +		debug("Skipping emulated SPI peripheral %s\n", name);
> +		goto fail_1;
> +	}
> +
> +	if (!vendor || !part_number || !guid) {
> +		debug("Skipping SPI peripheral %s\n", name);
> +		status = EFI_UNSUPPORTED;
> +		goto fail_1;
> +	}
> +
> +	if (!device_active(dev)) {
> +		int ret = device_probe(dev);
> +		if (ret) {
> +			debug("Skipping SPI peripheral %s, probe failed\n",
> +			      name);
> +			goto fail_1;
> +		}
> +	}
> +
> +	target = dev_get_parent_priv(dev);
> +	if (!target) {
> +		debug("Skipping uninitialized SPI peripheral %s\n", name);
> +		status = EFI_UNSUPPORTED;
> +		goto fail_1;
> +	}
> +
> +	debug("Registering SPI dev %d:%d, name %s\n",
> +	      dev_bus->seq_, spi_chip_select(dev), name);
> +
> +	priv = calloc(1, sizeof(*priv));
> +	if (!priv) {
> +		status = EFI_OUT_OF_RESOURCES;
> +		goto fail_1;
> +	}
> +
> +	vendor_utf16 = efi_convert_string(vendor);
> +	if (!vendor_utf16) {
> +		status = EFI_OUT_OF_RESOURCES;
> +		goto fail_2;
> +	}
> +
> +	part_number_utf16 = efi_convert_string(part_number);
> +	if (!part_number_utf16) {
> +		status = EFI_OUT_OF_RESOURCES;
> +		goto fail_3;
> +	}
> +
> +	name_utf16 = efi_convert_string(name);
> +	if (!name_utf16) {
> +		status = EFI_OUT_OF_RESOURCES;
> +		goto fail_4;
> +	}
> +
> +	priv->target = target;
> +
> +	efi_spi_init_part(&priv->part, target, vendor_utf16, part_number_utf16);
> +
> +	efi_spi_init_peripheral(&priv->peripheral, &priv->part,
> +				bus, target, guid, name_utf16);
> +
> +	efi_spi_append_peripheral(&priv->peripheral, bus);
> +
> +	efi_spi_init_io_protocol(&priv->io_protocol, &priv->peripheral, target);
> +
> +	status = efi_install_multiple_protocol_interfaces(&handle, guid,
> +							  &priv->io_protocol,
> +							  NULL);

There's a protocols installed here as well as in
efi_spi_protocol_register().  But I don't see those being uninstalled
somewhere.  Shouldn't destroy_efi_spi_bus() call
efi_uninstall_multiple_protocol_interfaces() as well ?


> +	if (status != EFI_SUCCESS)
> +		goto fail_5;
> +
> +	debug("Added EFI_SPI_IO_PROTOCOL for %s with guid %pUl\n", name, guid);
> +	return EFI_SUCCESS;
> +
> +fail_5:
> +	free(name_utf16);
> +fail_4:
> +	free(part_number_utf16);
> +fail_3:
> +	free(vendor_utf16);
> +fail_2:
> +	free(priv);
> +fail_1:
> +	return status;
> +}
> +
> +static struct efi_spi_bus *export_spi_bus(int i)
> +{
> +	struct efi_spi_bus *bus;
> +	struct udevice *dev, *child;
> +	const char *name;
> +	int r;
> +
> +	r = uclass_get_device(UCLASS_SPI, i, &dev);
> +	if (r < 0) {
> +		debug("Failed to get SPI bus %d\n", i);
> +		goto fail_1;
> +	}
> +
> +	name = dev_read_name(dev);
> +	debug("Registering SPI bus %d, name %s\n", i, name);
> +
> +	bus = calloc(1, sizeof(*bus));
> +	if (!bus)
> +		goto fail_1;
> +
> +	bus->friendly_name = efi_convert_string(name);
> +	if (!bus->friendly_name)
> +		goto fail_2;
> +
> +	bus->peripheral_list = NULL;
> +	bus->clock = efi_spi_bus_clock;
> +	bus->clock_parameter = NULL;
> +
> +	/* For the purposes of the current implementation, we do not need to
> +	 * expose the hardware device path to users of the SPI I/O protocol.
> +	 */
> +	bus->controller_path = &null_device_path;
> +
> +	device_foreach_child(child, dev) {
> +		efi_status_t status = export_spi_peripheral(bus, child);
> +
> +		if (status == EFI_OUT_OF_RESOURCES)
> +			goto fail_3;
> +	}
> +
> +	return bus;
> +
> +fail_3:
> +	destroy_efi_spi_bus(bus);
> +fail_2:
> +	free(bus);
> +fail_1:
> +	return NULL;
> +}
> +
> +efi_status_t efi_spi_protocol_register(void)
> +{
> +	efi_status_t status;
> +	efi_handle_t handle = NULL;
> +	struct efi_spi_configuration_protocol *proto;
> +	uint i;
> +
> +	debug("Registering EFI_SPI_CONFIGURATION_PROTOCOL\n");
> +
> +	proto = calloc(1, sizeof(*proto));
> +	if (!proto) {
> +		status = EFI_OUT_OF_RESOURCES;
> +		goto fail_1;
> +	}
> +
> +	proto->bus_count = uclass_id_count(UCLASS_SPI);
> +	proto->bus_list = calloc(proto->bus_count, sizeof(*proto->bus_list));
> +	if (!proto->bus_list) {
> +		status = EFI_OUT_OF_RESOURCES;
> +		goto fail_2;
> +	}
> +
> +	for (i = 0; i < proto->bus_count; i++) {
> +		proto->bus_list[i] = export_spi_bus(i);
> +		if (!proto->bus_list[i])
> +			goto fail_3;
> +	}
> +
> +	status = efi_install_multiple_protocol_interfaces(&handle,
> +							  &efi_spi_configuration_guid,
> +							  proto, NULL);
> +	if (status != EFI_SUCCESS)
> +		goto fail_3;
> +
> +	return EFI_SUCCESS;
> +
> +fail_3:
> +	for (i = 0; i < proto->bus_count; i++) {
> +		if (proto->bus_list[i])
> +			destroy_efi_spi_bus(proto->bus_list[i]);
> +	}
> +	free(proto->bus_list);
> +fail_2:
> +	free(proto);
> +fail_1:
> +	return status;
> +}
> diff --git a/lib/uuid.c b/lib/uuid.c
> index 465e1ac38f57..3f723b732588 100644
> --- a/lib/uuid.c
> +++ b/lib/uuid.c
> @@ -187,6 +187,10 @@ static const struct {
>  		"TCG2",
>  		EFI_TCG2_PROTOCOL_GUID,
>  		},
> +	{
> +		"SPI Protocol Stack",
> +		EFI_SPI_CONFIGURATION_GUID
> +	},
>  	{
>  		"System Partition",
>  		PARTITION_SYSTEM_GUID
> --
> 2.25.1
>


Regards
/Ilias
Simon Glass Dec. 14, 2022, 4:39 a.m. UTC | #2
Hi Paul,

On Wed, 23 Nov 2022 at 10:50, Paul Barker <paul.barker@sancloud.com> wrote:
>
> This addition allows UEFI applications running under u-boot to access
> peripherals on SPI busses. It is based on the UEFI Platform
> Initialization (PI) Specification, Version 1.7 Errata A (April 2020).
> Only the core functionality required to discover SPI peripherals and
> communicate with them is currently implemented. Other functionality such
> as the legacy SPI controller interface and the ability to update the SPI
> peripheral object associated with a particular SPI I/O protocol object
> is currently unimplemented.
>
> The following protocols are defined:
> * EFI_SPI_CONFIGURATION_PROTOCOL
> * EFI_SPI_IO_PROTOCOL
> * EFI_LEGACY_SPI_CONTROLLER_PROTOCOL
>
> Since there are no open source implementations of these protocols to use
> as an example, educated guesses/hacks have been made in cases where the
> UEFI PI specification is unclear and these are documented in comments.
>
> This implementation has been tested on the SanCloud BBE Lite and allowed
> a UEFI test application to successfully communicate with a Micron
> Authenta flash device connected via the SPI bus.
>
> Signed-off-by: Paul Barker <paul.barker@sancloud.com>
> ---
>  MAINTAINERS                       |   6 +
>  configs/am335x_evm_defconfig      |   1 +
>  include/efi_api.h                 |   4 +
>  include/efi_loader.h              |   4 +
>  include/efi_spi_protocol.h        | 166 +++++++++
>  lib/efi_loader/Kconfig            |   8 +
>  lib/efi_loader/Makefile           |   1 +
>  lib/efi_loader/efi_setup.c        |   6 +
>  lib/efi_loader/efi_spi_protocol.c | 576 ++++++++++++++++++++++++++++++
>  lib/uuid.c                        |   4 +
>  10 files changed, 776 insertions(+)
>  create mode 100644 include/efi_spi_protocol.h
>  create mode 100644 lib/efi_loader/efi_spi_protocol.c

This should have a sandbox test as well. See test/dm/spi.c or sf.c for examples.

Regards,
Simon
Paul Barker Dec. 14, 2022, 9:57 a.m. UTC | #3
On 14/12/2022 04:39, Simon Glass wrote:
> Hi Paul,
> 
> On Wed, 23 Nov 2022 at 10:50, Paul Barker <paul.barker@sancloud.com> wrote:
>>
>> This addition allows UEFI applications running under u-boot to access
>> peripherals on SPI busses. It is based on the UEFI Platform
>> Initialization (PI) Specification, Version 1.7 Errata A (April 2020).
>> Only the core functionality required to discover SPI peripherals and
>> communicate with them is currently implemented. Other functionality such
>> as the legacy SPI controller interface and the ability to update the SPI
>> peripheral object associated with a particular SPI I/O protocol object
>> is currently unimplemented.
>>
>> The following protocols are defined:
>> * EFI_SPI_CONFIGURATION_PROTOCOL
>> * EFI_SPI_IO_PROTOCOL
>> * EFI_LEGACY_SPI_CONTROLLER_PROTOCOL
>>
>> Since there are no open source implementations of these protocols to use
>> as an example, educated guesses/hacks have been made in cases where the
>> UEFI PI specification is unclear and these are documented in comments.
>>
>> This implementation has been tested on the SanCloud BBE Lite and allowed
>> a UEFI test application to successfully communicate with a Micron
>> Authenta flash device connected via the SPI bus.
>>
>> Signed-off-by: Paul Barker <paul.barker@sancloud.com>
>> ---
>>  MAINTAINERS                       |   6 +
>>  configs/am335x_evm_defconfig      |   1 +
>>  include/efi_api.h                 |   4 +
>>  include/efi_loader.h              |   4 +
>>  include/efi_spi_protocol.h        | 166 +++++++++
>>  lib/efi_loader/Kconfig            |   8 +
>>  lib/efi_loader/Makefile           |   1 +
>>  lib/efi_loader/efi_setup.c        |   6 +
>>  lib/efi_loader/efi_spi_protocol.c | 576 ++++++++++++++++++++++++++++++
>>  lib/uuid.c                        |   4 +
>>  10 files changed, 776 insertions(+)
>>  create mode 100644 include/efi_spi_protocol.h
>>  create mode 100644 lib/efi_loader/efi_spi_protocol.c
> 
> This should have a sandbox test as well. See test/dm/spi.c or sf.c for examples.

Hi Simon,

There is a test case in patch 2 of this series, is that what you're
looking for? It was split out as requested in the review of v3 of this
series.

Thanks,
Simon Glass Dec. 15, 2022, 2:24 p.m. UTC | #4
Yes, that's it. Thanks.


- Simon

On Wed, 14 Dec 2022 at 01:57, Paul Barker <paul.barker@sancloud.com> wrote:
>
> On 14/12/2022 04:39, Simon Glass wrote:
> > Hi Paul,
> >
> > On Wed, 23 Nov 2022 at 10:50, Paul Barker <paul.barker@sancloud.com> wrote:
> >>
> >> This addition allows UEFI applications running under u-boot to access
> >> peripherals on SPI busses. It is based on the UEFI Platform
> >> Initialization (PI) Specification, Version 1.7 Errata A (April 2020).
> >> Only the core functionality required to discover SPI peripherals and
> >> communicate with them is currently implemented. Other functionality such
> >> as the legacy SPI controller interface and the ability to update the SPI
> >> peripheral object associated with a particular SPI I/O protocol object
> >> is currently unimplemented.
> >>
> >> The following protocols are defined:
> >> * EFI_SPI_CONFIGURATION_PROTOCOL
> >> * EFI_SPI_IO_PROTOCOL
> >> * EFI_LEGACY_SPI_CONTROLLER_PROTOCOL
> >>
> >> Since there are no open source implementations of these protocols to use
> >> as an example, educated guesses/hacks have been made in cases where the
> >> UEFI PI specification is unclear and these are documented in comments.
> >>
> >> This implementation has been tested on the SanCloud BBE Lite and allowed
> >> a UEFI test application to successfully communicate with a Micron
> >> Authenta flash device connected via the SPI bus.
> >>
> >> Signed-off-by: Paul Barker <paul.barker@sancloud.com>
> >> ---
> >>  MAINTAINERS                       |   6 +
> >>  configs/am335x_evm_defconfig      |   1 +
> >>  include/efi_api.h                 |   4 +
> >>  include/efi_loader.h              |   4 +
> >>  include/efi_spi_protocol.h        | 166 +++++++++
> >>  lib/efi_loader/Kconfig            |   8 +
> >>  lib/efi_loader/Makefile           |   1 +
> >>  lib/efi_loader/efi_setup.c        |   6 +
> >>  lib/efi_loader/efi_spi_protocol.c | 576 ++++++++++++++++++++++++++++++
> >>  lib/uuid.c                        |   4 +
> >>  10 files changed, 776 insertions(+)
> >>  create mode 100644 include/efi_spi_protocol.h
> >>  create mode 100644 lib/efi_loader/efi_spi_protocol.c
> >
> > This should have a sandbox test as well. See test/dm/spi.c or sf.c for examples.
>
> Hi Simon,
>
> There is a test case in patch 2 of this series, is that what you're
> looking for? It was split out as requested in the review of v3 of this
> series.
>
> Thanks,
>
> --
> Paul Barker
> Principal Software Engineer
> SanCloud Ltd
>
> e: paul.barker@sancloud.com
> w: https://sancloud.com/
>
Paul Barker Dec. 24, 2022, 12:25 p.m. UTC | #5
On 13/12/2022 07:15, Ilias Apalodimas wrote:
> Hi Paul,
> 
> Apologies for the delayed reply.
> 
> [...]
> 
>> +static efi_status_t
>> +export_spi_peripheral(struct efi_spi_bus *bus, struct udevice *dev)
>> +{
>> +	efi_string_t name_utf16, vendor_utf16, part_number_utf16;
>> +	struct efi_spi_peripheral_priv *priv;
>> +	efi_status_t status;
>> +	efi_handle_t handle = NULL;
>> +	struct udevice *dev_bus = dev->parent;
>> +	struct spi_slave *target;
>> +	const char *name = dev_read_name(dev);
>> +	const char *vendor = dev_read_string(dev, "u-boot,uefi-spi-vendor");
>> +	const char *part_number = dev_read_string(dev,
>> +			"u-boot,uefi-spi-part-number");
>> +	efi_guid_t *guid = (efi_guid_t *)dev_read_u8_array_ptr(dev,
>> +			"u-boot,uefi-spi-io-guid", 16);
>> +
>> +	if (device_get_uclass_id(dev) == UCLASS_SPI_EMUL) {
>> +		debug("Skipping emulated SPI peripheral %s\n", name);
>> +		goto fail_1;
>> +	}
>> +
>> +	if (!vendor || !part_number || !guid) {
>> +		debug("Skipping SPI peripheral %s\n", name);
>> +		status = EFI_UNSUPPORTED;
>> +		goto fail_1;
>> +	}
>> +
>> +	if (!device_active(dev)) {
>> +		int ret = device_probe(dev);
>> +		if (ret) {
>> +			debug("Skipping SPI peripheral %s, probe failed\n",
>> +			      name);
>> +			goto fail_1;
>> +		}
>> +	}
>> +
>> +	target = dev_get_parent_priv(dev);
>> +	if (!target) {
>> +		debug("Skipping uninitialized SPI peripheral %s\n", name);
>> +		status = EFI_UNSUPPORTED;
>> +		goto fail_1;
>> +	}
>> +
>> +	debug("Registering SPI dev %d:%d, name %s\n",
>> +	      dev_bus->seq_, spi_chip_select(dev), name);
>> +
>> +	priv = calloc(1, sizeof(*priv));
>> +	if (!priv) {
>> +		status = EFI_OUT_OF_RESOURCES;
>> +		goto fail_1;
>> +	}
>> +
>> +	vendor_utf16 = efi_convert_string(vendor);
>> +	if (!vendor_utf16) {
>> +		status = EFI_OUT_OF_RESOURCES;
>> +		goto fail_2;
>> +	}
>> +
>> +	part_number_utf16 = efi_convert_string(part_number);
>> +	if (!part_number_utf16) {
>> +		status = EFI_OUT_OF_RESOURCES;
>> +		goto fail_3;
>> +	}
>> +
>> +	name_utf16 = efi_convert_string(name);
>> +	if (!name_utf16) {
>> +		status = EFI_OUT_OF_RESOURCES;
>> +		goto fail_4;
>> +	}
>> +
>> +	priv->target = target;
>> +
>> +	efi_spi_init_part(&priv->part, target, vendor_utf16, part_number_utf16);
>> +
>> +	efi_spi_init_peripheral(&priv->peripheral, &priv->part,
>> +				bus, target, guid, name_utf16);
>> +
>> +	efi_spi_append_peripheral(&priv->peripheral, bus);
>> +
>> +	efi_spi_init_io_protocol(&priv->io_protocol, &priv->peripheral, target);
>> +
>> +	status = efi_install_multiple_protocol_interfaces(&handle, guid,
>> +							  &priv->io_protocol,
>> +							  NULL);
> 
> There's a protocols installed here as well as in
> efi_spi_protocol_register().  But I don't see those being uninstalled
> somewhere.  Shouldn't destroy_efi_spi_bus() call
> efi_uninstall_multiple_protocol_interfaces() as well ?

Yes, `destroy_efi_spi_bus()` and `destroy_efi_spi_peripheral()`
should cleanup everything created by `export_spi_bus()` and
`export_spi_peripheral()` respectively.

I think we can just call `efi_delete_handle()` on the relevant handle in
`destroy_efi_spi_peripheral()` as that will remove all protocols anyway
and the call is simpler. I can make that change in v6 of the series.

Thanks,
Heinrich Schuchardt Dec. 24, 2022, 2:09 p.m. UTC | #6
On 12/24/22 13:25, Paul Barker wrote:
> On 13/12/2022 07:15, Ilias Apalodimas wrote:
>> Hi Paul,
>>
>> Apologies for the delayed reply.
>>
>> [...]
>>
>>> +static efi_status_t
>>> +export_spi_peripheral(struct efi_spi_bus *bus, struct udevice *dev)
>>> +{
>>> +	efi_string_t name_utf16, vendor_utf16, part_number_utf16;
>>> +	struct efi_spi_peripheral_priv *priv;
>>> +	efi_status_t status;
>>> +	efi_handle_t handle = NULL;
>>> +	struct udevice *dev_bus = dev->parent;
>>> +	struct spi_slave *target;
>>> +	const char *name = dev_read_name(dev);
>>> +	const char *vendor = dev_read_string(dev, "u-boot,uefi-spi-vendor");
>>> +	const char *part_number = dev_read_string(dev,
>>> +			"u-boot,uefi-spi-part-number");
>>> +	efi_guid_t *guid = (efi_guid_t *)dev_read_u8_array_ptr(dev,
>>> +			"u-boot,uefi-spi-io-guid", 16);
>>> +
>>> +	if (device_get_uclass_id(dev) == UCLASS_SPI_EMUL) {
>>> +		debug("Skipping emulated SPI peripheral %s\n", name);
>>> +		goto fail_1;
>>> +	}
>>> +
>>> +	if (!vendor || !part_number || !guid) {
>>> +		debug("Skipping SPI peripheral %s\n", name);
>>> +		status = EFI_UNSUPPORTED;
>>> +		goto fail_1;
>>> +	}
>>> +
>>> +	if (!device_active(dev)) {
>>> +		int ret = device_probe(dev);
>>> +		if (ret) {
>>> +			debug("Skipping SPI peripheral %s, probe failed\n",
>>> +			      name);
>>> +			goto fail_1;
>>> +		}
>>> +	}
>>> +
>>> +	target = dev_get_parent_priv(dev);
>>> +	if (!target) {
>>> +		debug("Skipping uninitialized SPI peripheral %s\n", name);
>>> +		status = EFI_UNSUPPORTED;
>>> +		goto fail_1;
>>> +	}
>>> +
>>> +	debug("Registering SPI dev %d:%d, name %s\n",
>>> +	      dev_bus->seq_, spi_chip_select(dev), name);
>>> +
>>> +	priv = calloc(1, sizeof(*priv));
>>> +	if (!priv) {
>>> +		status = EFI_OUT_OF_RESOURCES;
>>> +		goto fail_1;
>>> +	}
>>> +
>>> +	vendor_utf16 = efi_convert_string(vendor);
>>> +	if (!vendor_utf16) {
>>> +		status = EFI_OUT_OF_RESOURCES;
>>> +		goto fail_2;
>>> +	}
>>> +
>>> +	part_number_utf16 = efi_convert_string(part_number);
>>> +	if (!part_number_utf16) {
>>> +		status = EFI_OUT_OF_RESOURCES;
>>> +		goto fail_3;
>>> +	}
>>> +
>>> +	name_utf16 = efi_convert_string(name);
>>> +	if (!name_utf16) {
>>> +		status = EFI_OUT_OF_RESOURCES;
>>> +		goto fail_4;
>>> +	}
>>> +
>>> +	priv->target = target;
>>> +
>>> +	efi_spi_init_part(&priv->part, target, vendor_utf16, part_number_utf16);
>>> +
>>> +	efi_spi_init_peripheral(&priv->peripheral, &priv->part,
>>> +				bus, target, guid, name_utf16);
>>> +
>>> +	efi_spi_append_peripheral(&priv->peripheral, bus);
>>> +
>>> +	efi_spi_init_io_protocol(&priv->io_protocol, &priv->peripheral, target);
>>> +
>>> +	status = efi_install_multiple_protocol_interfaces(&handle, guid,
>>> +							  &priv->io_protocol,
>>> +							  NULL);
>>
>> There's a protocols installed here as well as in
>> efi_spi_protocol_register().  But I don't see those being uninstalled
>> somewhere.  Shouldn't destroy_efi_spi_bus() call
>> efi_uninstall_multiple_protocol_interfaces() as well ?
>
> Yes, `destroy_efi_spi_bus()` and `destroy_efi_spi_peripheral()`
> should cleanup everything created by `export_spi_bus()` and
> `export_spi_peripheral()` respectively.
>
> I think we can just call `efi_delete_handle()` on the relevant handle in
> `destroy_efi_spi_peripheral()` as that will remove all protocols anyway
> and the call is simpler. I can make that change in v6 of the series.

This patch does not correctly interface with the driver model.

What we need is:

* When a SPI flash device is probed successfully this causes the
installation of the protocols and thereby the creation of the handle.
* When trying to remove a SPI flash device this causes the
uninstallation of the protocols.

For interfacing with the driver model you should use the events
EVT_DM_POST_PROBE and EVT_DM_PRE_REMOVE. Cf. efi_bl_init().

When the driver model tries to remove the SPI device you have to call
UninstallMultipleProtocolInterfaces().

efi_delete_handle() does not check if one of the protocols installed on
the handles has been opened (e.g. with BY_DRIVER) and therefore cannot
request those parts of the loaded code that still hold references to the
protocol interfaces to close the protocols. This may lead to crashes.

If UninstallMultipleProtocolInterfaces() returns an error, the remove
event handler must return an error to avoid the removal of the device.

Best regards

Heinrich
Paul Barker Jan. 11, 2023, 1:02 p.m. UTC | #7
On 24/12/2022 14:09, Heinrich Schuchardt wrote:
> On 12/24/22 13:25, Paul Barker wrote:
>> On 13/12/2022 07:15, Ilias Apalodimas wrote:
>>> Hi Paul,
>>>
>>> Apologies for the delayed reply.
>>>
>>> [...]
>>>
>>>> +static efi_status_t
>>>> +export_spi_peripheral(struct efi_spi_bus *bus, struct udevice *dev)
>>>> +{
>>>> +    efi_string_t name_utf16, vendor_utf16, part_number_utf16;
>>>> +    struct efi_spi_peripheral_priv *priv;
>>>> +    efi_status_t status;
>>>> +    efi_handle_t handle = NULL;
>>>> +    struct udevice *dev_bus = dev->parent;
>>>> +    struct spi_slave *target;
>>>> +    const char *name = dev_read_name(dev);
>>>> +    const char *vendor = dev_read_string(dev, "u-boot,uefi-spi-vendor");
>>>> +    const char *part_number = dev_read_string(dev,
>>>> +            "u-boot,uefi-spi-part-number");
>>>> +    efi_guid_t *guid = (efi_guid_t *)dev_read_u8_array_ptr(dev,
>>>> +            "u-boot,uefi-spi-io-guid", 16);
>>>> +
>>>> +    if (device_get_uclass_id(dev) == UCLASS_SPI_EMUL) {
>>>> +        debug("Skipping emulated SPI peripheral %s\n", name);
>>>> +        goto fail_1;
>>>> +    }
>>>> +
>>>> +    if (!vendor || !part_number || !guid) {
>>>> +        debug("Skipping SPI peripheral %s\n", name);
>>>> +        status = EFI_UNSUPPORTED;
>>>> +        goto fail_1;
>>>> +    }
>>>> +
>>>> +    if (!device_active(dev)) {
>>>> +        int ret = device_probe(dev);
>>>> +        if (ret) {
>>>> +            debug("Skipping SPI peripheral %s, probe failed\n",
>>>> +                  name);
>>>> +            goto fail_1;
>>>> +        }
>>>> +    }
>>>> +
>>>> +    target = dev_get_parent_priv(dev);
>>>> +    if (!target) {
>>>> +        debug("Skipping uninitialized SPI peripheral %s\n", name);
>>>> +        status = EFI_UNSUPPORTED;
>>>> +        goto fail_1;
>>>> +    }
>>>> +
>>>> +    debug("Registering SPI dev %d:%d, name %s\n",
>>>> +          dev_bus->seq_, spi_chip_select(dev), name);
>>>> +
>>>> +    priv = calloc(1, sizeof(*priv));
>>>> +    if (!priv) {
>>>> +        status = EFI_OUT_OF_RESOURCES;
>>>> +        goto fail_1;
>>>> +    }
>>>> +
>>>> +    vendor_utf16 = efi_convert_string(vendor);
>>>> +    if (!vendor_utf16) {
>>>> +        status = EFI_OUT_OF_RESOURCES;
>>>> +        goto fail_2;
>>>> +    }
>>>> +
>>>> +    part_number_utf16 = efi_convert_string(part_number);
>>>> +    if (!part_number_utf16) {
>>>> +        status = EFI_OUT_OF_RESOURCES;
>>>> +        goto fail_3;
>>>> +    }
>>>> +
>>>> +    name_utf16 = efi_convert_string(name);
>>>> +    if (!name_utf16) {
>>>> +        status = EFI_OUT_OF_RESOURCES;
>>>> +        goto fail_4;
>>>> +    }
>>>> +
>>>> +    priv->target = target;
>>>> +
>>>> +    efi_spi_init_part(&priv->part, target, vendor_utf16, part_number_utf16);
>>>> +
>>>> +    efi_spi_init_peripheral(&priv->peripheral, &priv->part,
>>>> +                bus, target, guid, name_utf16);
>>>> +
>>>> +    efi_spi_append_peripheral(&priv->peripheral, bus);
>>>> +
>>>> +    efi_spi_init_io_protocol(&priv->io_protocol, &priv->peripheral, target);
>>>> +
>>>> +    status = efi_install_multiple_protocol_interfaces(&handle, guid,
>>>> +                              &priv->io_protocol,
>>>> +                              NULL);
>>>
>>> There's a protocols installed here as well as in
>>> efi_spi_protocol_register().  But I don't see those being uninstalled
>>> somewhere.  Shouldn't destroy_efi_spi_bus() call
>>> efi_uninstall_multiple_protocol_interfaces() as well ?
>>
>> Yes, `destroy_efi_spi_bus()` and `destroy_efi_spi_peripheral()`
>> should cleanup everything created by `export_spi_bus()` and
>> `export_spi_peripheral()` respectively.
>>
>> I think we can just call `efi_delete_handle()` on the relevant handle in
>> `destroy_efi_spi_peripheral()` as that will remove all protocols anyway
>> and the call is simpler. I can make that change in v6 of the series.
> 
> This patch does not correctly interface with the driver model.
> 
> What we need is:
> 
> * When a SPI flash device is probed successfully this causes the
> installation of the protocols and thereby the creation of the handle.
> * When trying to remove a SPI flash device this causes the
> uninstallation of the protocols.
> 
> For interfacing with the driver model you should use the events
> EVT_DM_POST_PROBE and EVT_DM_PRE_REMOVE. Cf. efi_bl_init().
> 
> When the driver model tries to remove the SPI device you have to call
> UninstallMultipleProtocolInterfaces().
> 
> efi_delete_handle() does not check if one of the protocols installed on
> the handles has been opened (e.g. with BY_DRIVER) and therefore cannot
> request those parts of the loaded code that still hold references to the
> protocol interfaces to close the protocols. This may lead to crashes.
> 
> If UninstallMultipleProtocolInterfaces() returns an error, the remove
> event handler must return an error to avoid the removal of the device.

Hi Heinrich,

I appreciate your feedback, though I have to say I'm disappointed that
this feedback wasn't provided on an earlier iteration of the patch
series.

I've got some other pressing priorities this week but will try to
re-write the relevant functions next week and re-submit.

Thanks,
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 09edf8883d3a..6a82cdbbb474 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -926,6 +926,12 @@  F:	tools/efivar.py
 F:	tools/file2include.c
 F:	tools/mkeficapsule.c
 
+EFI SPI SUPPORT
+M:	Paul Barker <paul.barker@sancloud.com>
+S:	Maintained
+F:	include/efi_spi_protocol.h
+F:	lib/efi_loader/efi_spi_protocol.c
+
 ENVIRONMENT
 M:	Joe Hershberger <joe.hershberger@ni.com>
 R:	Wolfgang Denk <wd@denx.de>
diff --git a/configs/am335x_evm_defconfig b/configs/am335x_evm_defconfig
index f73123e0b71d..ca8f4e62246a 100644
--- a/configs/am335x_evm_defconfig
+++ b/configs/am335x_evm_defconfig
@@ -123,3 +123,4 @@  CONFIG_WDT=y
 CONFIG_DYNAMIC_CRC_TABLE=y
 CONFIG_RSA=y
 CONFIG_LZO=y
+CONFIG_EFI_SPI_PROTOCOL=y
diff --git a/include/efi_api.h b/include/efi_api.h
index 9bb0d44ac8d5..eebefbe1585a 100644
--- a/include/efi_api.h
+++ b/include/efi_api.h
@@ -454,6 +454,10 @@  struct efi_runtime_services {
 	EFI_GUID(0x607f766c, 0x7455, 0x42be, 0x93, \
 		 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f)
 
+#define EFI_SPI_CONFIGURATION_GUID  \
+	EFI_GUID(0x85a6d3e6, 0xb65b, 0x4afc,     \
+		 0xb3, 0x8f, 0xc6, 0xd5, 0x4a, 0xf6, 0xdd, 0xc8)
+
 #define RISCV_EFI_BOOT_PROTOCOL_GUID \
 	EFI_GUID(0xccd15fec, 0x6f73, 0x4eec, 0x83, \
 		 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf)
diff --git a/include/efi_loader.h b/include/efi_loader.h
index 0c6c95ba4641..da5edc4a843b 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -553,6 +553,10 @@  efi_status_t efi_rng_register(void);
 efi_status_t efi_tcg2_register(void);
 /* Called by efi_init_obj_list() to install RISCV_EFI_BOOT_PROTOCOL */
 efi_status_t efi_riscv_register(void);
+/* Called by efi_init_obj_list() to install EFI_SPI_CONFIGURATION_PROTOCOL &
+ * EFI_SPI_IO_PROTOCOL
+ */
+efi_status_t efi_spi_protocol_register(void);
 /* Called by efi_init_obj_list() to do initial measurement */
 efi_status_t efi_tcg2_do_initial_measurement(void);
 /* measure the pe-coff image, extend PCR and add Event Log */
diff --git a/include/efi_spi_protocol.h b/include/efi_spi_protocol.h
new file mode 100644
index 000000000000..1a247a6a6f85
--- /dev/null
+++ b/include/efi_spi_protocol.h
@@ -0,0 +1,166 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause-Patent */
+/*
+ * Copyright (c) 2017, Intel Corporation. All rights reserved.
+ * Copyright (c) 2022 Micron Technology, Inc.
+ *
+ * This header is based on code from EDK II, with modifications to match the
+ * u-boot coding style. It defines data structures for the following protocols
+ * found in the UEFI Platform Initialization (PI) Specification, version 1.7
+ * errata A:
+ *
+ *    - EFI_SPI_CONFIGURATION_PROTOCOL
+ *    - EFI_SPI_IO_PROTOCOL
+ *    - EFI_LEGACY_SPI_CONTROLLER_PROTOCOL (required by the I/O protocol as the
+ *      spec does not appear to allow the legacy_spi_protocol pointer to be NULL
+ *      in struct efi_spi_io_protocol)
+ */
+
+#ifndef _EFI_SPI_PROTOCOL_H
+#define _EFI_SPI_PROTOCOL_H
+
+#include <efi.h>
+#include <efi_api.h>
+
+struct efi_spi_peripheral;
+
+struct efi_spi_part {
+	efi_string_t vendor;
+	efi_string_t part_number;
+	u32 min_clock_hz;
+	u32 max_clock_hz;
+	bool chip_select_polarity;
+};
+
+struct efi_spi_bus {
+	efi_string_t friendly_name;
+	struct efi_spi_peripheral *peripheral_list;
+	struct efi_device_path *controller_path;
+
+	efi_status_t
+	(EFIAPI * clock)(const struct efi_spi_peripheral *spi_peripheral,
+		u32 *clock_hz);
+
+	void *clock_parameter;
+};
+
+struct efi_spi_peripheral {
+	struct efi_spi_peripheral *next_spi_peripheral;
+	efi_string_t friendly_name;
+	efi_guid_t *spi_peripheral_driver_guid;
+	struct efi_spi_part *spi_part;
+	u32 max_clock_hz;
+	bool clock_polarity;
+	bool clock_phase;
+	u32 attributes;
+	void *configuration_data;
+	struct efi_spi_bus *spi_bus;
+
+	efi_status_t
+	(EFIAPI * chip_select)(const struct efi_spi_peripheral *spi_peripheral,
+		bool pin_value);
+
+	void *chip_select_parameter;
+};
+
+struct efi_spi_configuration_protocol {
+	u32 bus_count;
+	struct efi_spi_bus **bus_list;
+};
+
+#define EFI_LEGACY_SPI_CONTROLLER_GUID  \
+	EFI_GUID(0x39136fc7, 0x1a11, 0x49de,         \
+		 0xbf, 0x35, 0x0e, 0x78, 0xdd, 0xb5, 0x24, 0xfc)
+
+struct efi_legacy_spi_controller_protocol;
+
+struct efi_legacy_spi_controller_protocol {
+	u32 maximum_offset;
+	u32 maximum_range_bytes;
+	u32 range_register_count;
+
+	efi_status_t
+	(EFIAPI * erase_block_opcode)(const struct efi_legacy_spi_controller_protocol *this,
+		u8 erase_block_opcode);
+
+	efi_status_t
+	(EFIAPI * write_status_prefix)(const struct efi_legacy_spi_controller_protocol *this,
+		u8 write_status_prefix);
+
+	efi_status_t
+	(EFIAPI * bios_base_address)(const struct efi_legacy_spi_controller_protocol *this,
+		u32 bios_base_address);
+
+	efi_status_t
+	(EFIAPI * clear_spi_protect)(const struct efi_legacy_spi_controller_protocol *this);
+
+	bool
+	(EFIAPI * is_range_protected)(const struct efi_legacy_spi_controller_protocol *this,
+		u32 bios_address,
+		u32 blocks_to_protect);
+
+	efi_status_t
+	(EFIAPI * protect_next_range)(const struct efi_legacy_spi_controller_protocol *this,
+		u32 bios_address,
+		u32 blocks_to_protect);
+
+	efi_status_t
+	(EFIAPI * lock_controller)(const struct efi_legacy_spi_controller_protocol *this);
+};
+
+struct efi_spi_io_protocol;
+
+enum efi_spi_transaction_type {
+	SPI_TRANSACTION_FULL_DUPLEX,
+	SPI_TRANSACTION_WRITE_ONLY,
+	SPI_TRANSACTION_READ_ONLY,
+	SPI_TRANSACTION_WRITE_THEN_READ
+};
+
+struct efi_spi_bus_transaction {
+	struct efi_spi_peripheral *spi_peripheral;
+	enum efi_spi_transaction_type transaction_type;
+	bool debug_transaction;
+	u32 bus_width;
+	u32 frame_size;
+	u32 write_bytes;
+	u8 *write_buffer;
+	u32 read_bytes;
+	u8 *read_buffer;
+};
+
+struct efi_spi_io_protocol {
+	struct efi_spi_peripheral *spi_peripheral;
+	struct efi_spi_peripheral *original_spi_peripheral;
+	u32 frame_size_support_mask;
+	u32 maximum_transfer_bytes;
+	u32 attributes;
+	struct efi_legacy_spi_controller_protocol *legacy_spi_protocol;
+
+	efi_status_t
+	(EFIAPI * transaction)(const struct efi_spi_io_protocol *this,
+		enum efi_spi_transaction_type transaction_type,
+		bool debug_transaction,
+		u32 clock_hz,
+		u32 bus_width,
+		u32 frame_size,
+		u32 write_bytes,
+		u8 *write_buffer,
+		u32 read_bytes,
+		u8 *read_buffer);
+
+	efi_status_t
+	(EFIAPI * update_spi_peripheral)(struct efi_spi_io_protocol *this,
+		struct efi_spi_peripheral *spi_peripheral);
+};
+
+struct efi_spi_peripheral_priv {
+	struct efi_spi_peripheral peripheral;
+	struct efi_spi_part part;
+	struct efi_spi_io_protocol io_protocol;
+	efi_handle_t handle;
+	struct spi_slave *target;
+};
+
+efi_status_t efi_spi_protocol_register(void);
+
+#endif
diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig
index e2b643871bf8..2e4d92ed01a3 100644
--- a/lib/efi_loader/Kconfig
+++ b/lib/efi_loader/Kconfig
@@ -405,4 +405,12 @@  config EFI_RISCV_BOOT_PROTOCOL
 	  replace the transfer via the device-tree. The latter is not
 	  possible on systems using ACPI.
 
+config EFI_SPI_PROTOCOL
+	bool "EFI SPI protocol support"
+	depends on DM_SPI
+	help
+	  Provide implementations of EFI_SPI_CONFIGURATION_PROTOCOL and
+	  EFI_SPI_IO_PROTOCOL to allow UEFI applications to access devices
+	  connected via SPI bus.
+
 endif
diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile
index 8738757dd2c1..e0a793103ba5 100644
--- a/lib/efi_loader/Makefile
+++ b/lib/efi_loader/Makefile
@@ -74,6 +74,7 @@  obj-$(CONFIG_GENERATE_SMBIOS_TABLE) += efi_smbios.o
 obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_rng.o
 obj-$(CONFIG_EFI_TCG2_PROTOCOL) += efi_tcg2.o
 obj-$(CONFIG_EFI_RISCV_BOOT_PROTOCOL) += efi_riscv.o
+obj-$(CONFIG_EFI_SPI_PROTOCOL) += efi_spi_protocol.o
 obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o
 obj-$(CONFIG_EFI_SIGNATURE_SUPPORT) += efi_signature.o
 obj-$(CONFIG_EFI_ECPT) += efi_conformance.o
diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c
index 543764113530..2a76e5731d2c 100644
--- a/lib/efi_loader/efi_setup.c
+++ b/lib/efi_loader/efi_setup.c
@@ -289,6 +289,12 @@  efi_status_t efi_init_obj_list(void)
 			goto out;
 	}
 
+	if (IS_ENABLED(CONFIG_EFI_SPI_PROTOCOL)) {
+		ret = efi_spi_protocol_register();
+		if (ret != EFI_SUCCESS)
+			goto out;
+	}
+
 	/* Secure boot */
 	ret = efi_init_secure_boot();
 	if (ret != EFI_SUCCESS)
diff --git a/lib/efi_loader/efi_spi_protocol.c b/lib/efi_loader/efi_spi_protocol.c
new file mode 100644
index 000000000000..50610b887cf2
--- /dev/null
+++ b/lib/efi_loader/efi_spi_protocol.c
@@ -0,0 +1,576 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2022 Micron Technology, Inc.
+ */
+
+#define LOG_CATEGORY LOGC_EFI
+
+#include <common.h>
+#include <dm/device.h>
+#include <dm/device-internal.h>
+#include <dm/read.h>
+#include <efi.h>
+#include <efi_loader.h>
+#include <efi_spi_protocol.h>
+#include <malloc.h>
+#include <spi.h>
+
+static void dump_buffer(const char *msg, u32 length, u8 *buffer)
+{
+	u32 i;
+	EFI_PRINT("%s %d bytes:", msg, length);
+	for (i = 0; i < length; i++)
+		EFI_PRINT(" %02x", buffer[i]);
+	EFI_PRINT("\n");
+}
+
+static efi_status_t EFIAPI
+efi_spi_bus_clock(const struct efi_spi_peripheral *spi_peripheral,
+		  u32 *clock_hz)
+{
+	EFI_ENTRY("%p, %p", spi_peripheral, clock_hz);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+efi_spi_peripheral_chip_select(const struct efi_spi_peripheral *spi_peripheral,
+			       bool pin_value)
+{
+	EFI_ENTRY("%p, %d", spi_peripheral, pin_value);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+legacy_erase_block_opcode(const struct efi_legacy_spi_controller_protocol *this,
+			  u8 erase_block_opcode)
+{
+	EFI_ENTRY("%p, %u", this, erase_block_opcode);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+legacy_write_status_prefix(const struct efi_legacy_spi_controller_protocol *this,
+			   u8 write_status_prefix)
+{
+	EFI_ENTRY("%p, %u", this, write_status_prefix);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+legacy_bios_base_address(const struct efi_legacy_spi_controller_protocol *this,
+			 u32 bios_base_address)
+{
+	EFI_ENTRY("%p, %u", this, bios_base_address);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+legacy_clear_spi_protect(const struct efi_legacy_spi_controller_protocol *this)
+{
+	EFI_ENTRY("%p", this);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static bool EFIAPI
+legacy_is_range_protected(const struct efi_legacy_spi_controller_protocol *this,
+			  u32 bios_address,
+			  u32 blocks_to_protect)
+{
+	EFI_ENTRY("%p, %u, %u", this, bios_address, blocks_to_protect);
+	return EFI_EXIT(false);
+}
+
+static efi_status_t EFIAPI
+legacy_protect_next_range(const struct efi_legacy_spi_controller_protocol *this,
+			  u32 bios_address,
+			  u32 blocks_to_protect)
+{
+	EFI_ENTRY("%p, %u, %u", this, bios_address, blocks_to_protect);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+legacy_lock_controller(const struct efi_legacy_spi_controller_protocol *this)
+{
+	EFI_ENTRY("%p", this);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+efi_spi_io_update_spi_peripheral(struct efi_spi_io_protocol *this,
+				 struct efi_spi_peripheral *spi_peripheral)
+{
+	EFI_ENTRY("%p, %p", this, spi_peripheral);
+	return EFI_EXIT(EFI_UNSUPPORTED);
+}
+
+static efi_status_t EFIAPI
+efi_spi_io_transaction(const struct efi_spi_io_protocol *this,
+		       enum efi_spi_transaction_type transaction_type,
+		       bool debug_transaction,
+		       u32 clock_hz,
+		       u32 bus_width,
+		       u32 frame_size,
+		       u32 write_bytes,
+		       u8 *write_buffer,
+		       u32 read_bytes,
+		       u8 *read_buffer)
+{
+	struct spi_slave *target;
+	efi_status_t status = EFI_SUCCESS;
+	int r;
+
+	/* We ignore the bus_width and frame_size arguments to this function as
+	 * the appropriate bus configuration for the connected device will be
+	 * performed during spi_claim_bus().
+	 */
+
+	/* TODO: Print transaction details if debug_transaction is true. */
+
+	EFI_ENTRY("%p, %u, %u, %u, %u, %u, %u, %p, %u, %p",
+		  this, transaction_type, debug_transaction,
+		  clock_hz, bus_width, frame_size,
+		  write_bytes, write_buffer, read_bytes, read_buffer);
+
+	if (!this)
+		return EFI_EXIT(EFI_INVALID_PARAMETER);
+
+	target = container_of(this, struct efi_spi_peripheral_priv, io_protocol)->target;
+
+	if (clock_hz > this->spi_peripheral->max_clock_hz)
+		return EFI_EXIT(EFI_UNSUPPORTED);
+
+	r = spi_claim_bus(target);
+	if (r != 0)
+		return EFI_EXIT(EFI_DEVICE_ERROR);
+	EFI_PRINT("SPI IO: Bus claimed\n");
+
+	if (clock_hz) {
+		EFI_PRINT("SPI IO: Setting clock rate to %u Hz\n", clock_hz);
+		spi_set_speed(target, clock_hz);
+	} else {
+		EFI_PRINT("SPI IO: Using default clock rate\n");
+	}
+
+	switch (transaction_type) {
+	case SPI_TRANSACTION_FULL_DUPLEX:
+		EFI_PRINT("SPI IO: Full-duplex\n");
+		if (write_bytes != read_bytes || !write_bytes || !write_buffer || !read_buffer) {
+			status = EFI_INVALID_PARAMETER;
+			break;
+		}
+		if (debug_transaction)
+			dump_buffer("SPI IO: write", write_bytes, write_buffer);
+		r = spi_xfer(target, 8 * write_bytes,
+			     write_buffer, read_buffer, SPI_XFER_ONCE);
+		EFI_PRINT("SPI IO: xfer returned %d\n", r);
+		if (debug_transaction)
+			dump_buffer("SPI IO: read", read_bytes, read_buffer);
+		status = (r == 0) ? EFI_SUCCESS : EFI_DEVICE_ERROR;
+		break;
+	case SPI_TRANSACTION_READ_ONLY:
+		EFI_PRINT("SPI IO: Read-only\n");
+		if (!read_bytes || !read_buffer) {
+			status = EFI_INVALID_PARAMETER;
+			break;
+		}
+		r = spi_xfer(target, 8 * read_bytes,
+			     NULL, read_buffer, SPI_XFER_ONCE);
+		EFI_PRINT("SPI IO: xfer returned %d\n", r);
+		if (debug_transaction)
+			dump_buffer("SPI IO: read", read_bytes, read_buffer);
+		status = (r == 0) ? EFI_SUCCESS : EFI_DEVICE_ERROR;
+		break;
+	case SPI_TRANSACTION_WRITE_ONLY:
+		EFI_PRINT("SPI IO: Write-only\n");
+		if (!write_bytes || !write_buffer) {
+			status = EFI_INVALID_PARAMETER;
+			break;
+		}
+		if (debug_transaction)
+			dump_buffer("SPI IO: write", write_bytes, write_buffer);
+		r = spi_xfer(target, 8 * write_bytes,
+			     write_buffer, NULL, SPI_XFER_ONCE);
+		EFI_PRINT("SPI IO: xfer returned %d\n", r);
+		status = (r == 0) ? EFI_SUCCESS : EFI_DEVICE_ERROR;
+		break;
+	case SPI_TRANSACTION_WRITE_THEN_READ:
+		EFI_PRINT("SPI IO: Write-then-read\n");
+		if (!write_bytes || !write_buffer || !read_bytes || !read_buffer) {
+			status = EFI_INVALID_PARAMETER;
+			break;
+		}
+		if (debug_transaction)
+			dump_buffer("SPI IO: write", write_bytes, write_buffer);
+		r = spi_xfer(target, 8 * write_bytes,
+			     write_buffer, NULL, SPI_XFER_BEGIN);
+		EFI_PRINT("SPI IO: xfer [1/2] returned %d\n", r);
+		if (r != 0) {
+			status = EFI_DEVICE_ERROR;
+			break;
+		}
+		r = spi_xfer(target, 8 * read_bytes,
+			     NULL, read_buffer, SPI_XFER_END);
+		EFI_PRINT("SPI IO: xfer [2/2] returned %d\n", r);
+		if (debug_transaction)
+			dump_buffer("SPI IO: read", read_bytes, read_buffer);
+		status = (r == 0) ? EFI_SUCCESS : EFI_DEVICE_ERROR;
+		break;
+	default:
+		status = EFI_INVALID_PARAMETER;
+		break;
+	}
+
+	spi_release_bus(target);
+	EFI_PRINT("SPI IO: Released bus\n");
+	return EFI_EXIT(status);
+}
+
+static struct efi_device_path null_device_path = {
+	.type     = DEVICE_PATH_TYPE_END,
+	.sub_type = DEVICE_PATH_SUB_TYPE_END,
+	.length   = 4
+};
+
+static struct efi_legacy_spi_controller_protocol
+dummy_legacy_spi_controller_protocol = {
+	.maximum_offset = 0,
+	.maximum_range_bytes = 0,
+	.range_register_count = 0,
+	.erase_block_opcode = legacy_erase_block_opcode,
+	.write_status_prefix = legacy_write_status_prefix,
+	.bios_base_address = legacy_bios_base_address,
+	.clear_spi_protect = legacy_clear_spi_protect,
+	.is_range_protected = legacy_is_range_protected,
+	.protect_next_range = legacy_protect_next_range,
+	.lock_controller = legacy_lock_controller
+};
+
+static efi_guid_t efi_spi_configuration_guid = EFI_SPI_CONFIGURATION_GUID;
+
+static void destroy_efi_spi_peripheral(struct efi_spi_peripheral *peripheral)
+{
+	struct efi_spi_peripheral_priv *priv =
+		container_of(peripheral,
+			     struct efi_spi_peripheral_priv,
+			     peripheral);
+	free(priv->peripheral.friendly_name);
+	free(priv->part.vendor);
+	free(priv->part.part_number);
+	free(priv);
+}
+
+static void destroy_efi_spi_bus(struct efi_spi_bus *bus)
+{
+	struct efi_spi_peripheral *peripheral = bus->peripheral_list;
+
+	while (peripheral) {
+		struct efi_spi_peripheral *next =
+			peripheral->next_spi_peripheral;
+		destroy_efi_spi_peripheral(peripheral);
+		peripheral = next;
+	}
+	free(bus->friendly_name);
+	free(bus);
+}
+
+static void
+efi_spi_init_part(struct efi_spi_part *part,
+		  struct spi_slave *target,
+		  efi_string_t vendor_utf16,
+		  efi_string_t part_number_utf16
+)
+{
+	part->vendor = vendor_utf16;
+	part->part_number = part_number_utf16;
+	part->min_clock_hz = 0;
+	part->max_clock_hz = target->max_hz;
+	part->chip_select_polarity =
+		(target->mode & SPI_CS_HIGH) ? true : false;
+}
+
+static void
+efi_spi_init_peripheral(struct efi_spi_peripheral *peripheral,
+			struct efi_spi_part *part,
+			struct efi_spi_bus *bus,
+			struct spi_slave *target,
+			efi_guid_t *guid,
+			efi_string_t name_utf16
+)
+{
+	peripheral->friendly_name = name_utf16;
+	peripheral->spi_part = part;
+	peripheral->next_spi_peripheral = NULL;
+	peripheral->spi_peripheral_driver_guid = guid;
+	peripheral->max_clock_hz = target->max_hz;
+	peripheral->clock_polarity = (target->mode & SPI_CPOL) ? true : false;
+	peripheral->clock_phase = (target->mode & SPI_CPHA) ? true : false;
+	peripheral->attributes = 0;
+	peripheral->configuration_data = NULL;
+	peripheral->spi_bus = bus;
+	peripheral->chip_select = efi_spi_peripheral_chip_select;
+	peripheral->chip_select_parameter = NULL;
+}
+
+static void
+efi_spi_append_peripheral(struct efi_spi_peripheral *peripheral,
+			  struct efi_spi_bus *bus
+)
+{
+	if (bus->peripheral_list) {
+		struct efi_spi_peripheral *tmp = bus->peripheral_list;
+
+		while (tmp->next_spi_peripheral)
+			tmp = tmp->next_spi_peripheral;
+
+		tmp->next_spi_peripheral = peripheral;
+	} else {
+		bus->peripheral_list = peripheral;
+	}
+}
+
+static void
+efi_spi_init_io_protocol(struct efi_spi_io_protocol *io_protocol,
+			 struct efi_spi_peripheral *peripheral,
+			 struct spi_slave *target
+)
+{
+	u32 max_read, max_write;
+
+	io_protocol->spi_peripheral = peripheral;
+	io_protocol->original_spi_peripheral = peripheral;
+	io_protocol->legacy_spi_protocol =
+		&dummy_legacy_spi_controller_protocol;
+	io_protocol->transaction = efi_spi_io_transaction;
+	io_protocol->update_spi_peripheral = efi_spi_io_update_spi_peripheral;
+
+	/* This is a bit of a hack. The EFI data structures do not allow us to
+	 * represent a frame size greater than 32 bits.
+	 */
+	if (target->wordlen <= 32)
+		io_protocol->frame_size_support_mask =
+			1 << (target->wordlen - 1);
+	else
+		io_protocol->frame_size_support_mask = 0;
+
+	/* Again, this is a bit of a hack. The EFI data structures only allow
+	 * for a single maximum transfer size whereas the u-boot spi_slave
+	 * structure records maximum read transfer size and maximum write
+	 * transfer size separately. So we need to take the minimum of these two
+	 * values.
+	 *
+	 * In u-boot, a value of zero for these fields means there is no limit
+	 * on the transfer size. However in the UEFI PI spec a value of zero is
+	 * invalid so we'll use 0xFFFFFFFF as a substitute unlimited value.
+	 */
+	max_write =
+		target->max_write_size ? target->max_write_size : 0xFFFFFFFF;
+	max_read = target->max_read_size ? target->max_read_size : 0xFFFFFFFF;
+	io_protocol->maximum_transfer_bytes =
+		(max_read > max_write) ? max_write : max_read;
+
+	/* Hack++. Leave attributes set to zero since the flags listed in the
+	 * UEFI PI spec have no defined numerical values and so cannot be used.
+	 */
+	io_protocol->attributes = 0;
+}
+
+static efi_status_t
+export_spi_peripheral(struct efi_spi_bus *bus, struct udevice *dev)
+{
+	efi_string_t name_utf16, vendor_utf16, part_number_utf16;
+	struct efi_spi_peripheral_priv *priv;
+	efi_status_t status;
+	efi_handle_t handle = NULL;
+	struct udevice *dev_bus = dev->parent;
+	struct spi_slave *target;
+	const char *name = dev_read_name(dev);
+	const char *vendor = dev_read_string(dev, "u-boot,uefi-spi-vendor");
+	const char *part_number = dev_read_string(dev,
+			"u-boot,uefi-spi-part-number");
+	efi_guid_t *guid = (efi_guid_t *)dev_read_u8_array_ptr(dev,
+			"u-boot,uefi-spi-io-guid", 16);
+
+	if (device_get_uclass_id(dev) == UCLASS_SPI_EMUL) {
+		debug("Skipping emulated SPI peripheral %s\n", name);
+		goto fail_1;
+	}
+
+	if (!vendor || !part_number || !guid) {
+		debug("Skipping SPI peripheral %s\n", name);
+		status = EFI_UNSUPPORTED;
+		goto fail_1;
+	}
+
+	if (!device_active(dev)) {
+		int ret = device_probe(dev);
+		if (ret) {
+			debug("Skipping SPI peripheral %s, probe failed\n",
+			      name);
+			goto fail_1;
+		}
+	}
+
+	target = dev_get_parent_priv(dev);
+	if (!target) {
+		debug("Skipping uninitialized SPI peripheral %s\n", name);
+		status = EFI_UNSUPPORTED;
+		goto fail_1;
+	}
+
+	debug("Registering SPI dev %d:%d, name %s\n",
+	      dev_bus->seq_, spi_chip_select(dev), name);
+
+	priv = calloc(1, sizeof(*priv));
+	if (!priv) {
+		status = EFI_OUT_OF_RESOURCES;
+		goto fail_1;
+	}
+
+	vendor_utf16 = efi_convert_string(vendor);
+	if (!vendor_utf16) {
+		status = EFI_OUT_OF_RESOURCES;
+		goto fail_2;
+	}
+
+	part_number_utf16 = efi_convert_string(part_number);
+	if (!part_number_utf16) {
+		status = EFI_OUT_OF_RESOURCES;
+		goto fail_3;
+	}
+
+	name_utf16 = efi_convert_string(name);
+	if (!name_utf16) {
+		status = EFI_OUT_OF_RESOURCES;
+		goto fail_4;
+	}
+
+	priv->target = target;
+
+	efi_spi_init_part(&priv->part, target, vendor_utf16, part_number_utf16);
+
+	efi_spi_init_peripheral(&priv->peripheral, &priv->part,
+				bus, target, guid, name_utf16);
+
+	efi_spi_append_peripheral(&priv->peripheral, bus);
+
+	efi_spi_init_io_protocol(&priv->io_protocol, &priv->peripheral, target);
+
+	status = efi_install_multiple_protocol_interfaces(&handle, guid,
+							  &priv->io_protocol,
+							  NULL);
+	if (status != EFI_SUCCESS)
+		goto fail_5;
+
+	debug("Added EFI_SPI_IO_PROTOCOL for %s with guid %pUl\n", name, guid);
+	return EFI_SUCCESS;
+
+fail_5:
+	free(name_utf16);
+fail_4:
+	free(part_number_utf16);
+fail_3:
+	free(vendor_utf16);
+fail_2:
+	free(priv);
+fail_1:
+	return status;
+}
+
+static struct efi_spi_bus *export_spi_bus(int i)
+{
+	struct efi_spi_bus *bus;
+	struct udevice *dev, *child;
+	const char *name;
+	int r;
+
+	r = uclass_get_device(UCLASS_SPI, i, &dev);
+	if (r < 0) {
+		debug("Failed to get SPI bus %d\n", i);
+		goto fail_1;
+	}
+
+	name = dev_read_name(dev);
+	debug("Registering SPI bus %d, name %s\n", i, name);
+
+	bus = calloc(1, sizeof(*bus));
+	if (!bus)
+		goto fail_1;
+
+	bus->friendly_name = efi_convert_string(name);
+	if (!bus->friendly_name)
+		goto fail_2;
+
+	bus->peripheral_list = NULL;
+	bus->clock = efi_spi_bus_clock;
+	bus->clock_parameter = NULL;
+
+	/* For the purposes of the current implementation, we do not need to
+	 * expose the hardware device path to users of the SPI I/O protocol.
+	 */
+	bus->controller_path = &null_device_path;
+
+	device_foreach_child(child, dev) {
+		efi_status_t status = export_spi_peripheral(bus, child);
+
+		if (status == EFI_OUT_OF_RESOURCES)
+			goto fail_3;
+	}
+
+	return bus;
+
+fail_3:
+	destroy_efi_spi_bus(bus);
+fail_2:
+	free(bus);
+fail_1:
+	return NULL;
+}
+
+efi_status_t efi_spi_protocol_register(void)
+{
+	efi_status_t status;
+	efi_handle_t handle = NULL;
+	struct efi_spi_configuration_protocol *proto;
+	uint i;
+
+	debug("Registering EFI_SPI_CONFIGURATION_PROTOCOL\n");
+
+	proto = calloc(1, sizeof(*proto));
+	if (!proto) {
+		status = EFI_OUT_OF_RESOURCES;
+		goto fail_1;
+	}
+
+	proto->bus_count = uclass_id_count(UCLASS_SPI);
+	proto->bus_list = calloc(proto->bus_count, sizeof(*proto->bus_list));
+	if (!proto->bus_list) {
+		status = EFI_OUT_OF_RESOURCES;
+		goto fail_2;
+	}
+
+	for (i = 0; i < proto->bus_count; i++) {
+		proto->bus_list[i] = export_spi_bus(i);
+		if (!proto->bus_list[i])
+			goto fail_3;
+	}
+
+	status = efi_install_multiple_protocol_interfaces(&handle,
+							  &efi_spi_configuration_guid,
+							  proto, NULL);
+	if (status != EFI_SUCCESS)
+		goto fail_3;
+
+	return EFI_SUCCESS;
+
+fail_3:
+	for (i = 0; i < proto->bus_count; i++) {
+		if (proto->bus_list[i])
+			destroy_efi_spi_bus(proto->bus_list[i]);
+	}
+	free(proto->bus_list);
+fail_2:
+	free(proto);
+fail_1:
+	return status;
+}
diff --git a/lib/uuid.c b/lib/uuid.c
index 465e1ac38f57..3f723b732588 100644
--- a/lib/uuid.c
+++ b/lib/uuid.c
@@ -187,6 +187,10 @@  static const struct {
 		"TCG2",
 		EFI_TCG2_PROTOCOL_GUID,
 		},
+	{
+		"SPI Protocol Stack",
+		EFI_SPI_CONFIGURATION_GUID
+	},
 	{
 		"System Partition",
 		PARTITION_SYSTEM_GUID