[PATCH v3] drivers: fastboot: Add support for SPI flash memory
Mattijs Korpershoek
mkorpershoek at kernel.org
Fri Jul 18 10:10:37 CEST 2025
Hi Ariel,
Thank you for the patch.
On Thu, Jul 17, 2025 at 10:58, Ariel D'Alessandro <ariel.dalessandro at collabora.com> wrote:
> Fastboot currently supports MMC and NAND flash devices. Similarly,
> extend the support to SPI flash memories.
>
> Note that in this initial implementation, partitions on the device are
> not supported yet, but raw partitions can be set in U-Boot environment.
>
> To define a raw partition descriptor, add an environment variable
> similar to the MMC case:
>
> ```
> fastboot_raw_partition_<raw partition name>=<offset> <size>
> ```
>
> for example:
>
> ```
> fastboot_raw_partition_boot=0x0 0x1000000
> ```
>
> Signed-off-by: Ariel D'Alessandro <ariel.dalessandro at collabora.com>
Thanks for the doc updates. This looks great!
Reviewed-by: Mattijs Korpershoek <mkorpershoek at kernel.org>
> ---
> doc/android/fastboot.rst | 16 +-
> drivers/fastboot/Kconfig | 6 +-
> drivers/fastboot/Makefile | 1 +
> drivers/fastboot/fb_command.c | 8 +
> drivers/fastboot/fb_getvar.c | 6 +
> drivers/fastboot/fb_spi_flash.c | 251 ++++++++++++++++++++++++++++++++
> include/fb_spi_flash.h | 42 ++++++
> 7 files changed, 326 insertions(+), 4 deletions(-)
> create mode 100644 drivers/fastboot/fb_spi_flash.c
> create mode 100644 include/fb_spi_flash.h
>
> diff --git a/doc/android/fastboot.rst b/doc/android/fastboot.rst
> index 6f92cd28eb1..19e2ee9d407 100644
> --- a/doc/android/fastboot.rst
> +++ b/doc/android/fastboot.rst
> @@ -32,7 +32,7 @@ The following OEM commands are supported (if enabled):
> - ``oem console`` - this dumps U-Boot console record buffer
> - ``oem board`` - this executes a custom board function which is defined by the vendor
>
> -Support for both eMMC and NAND devices is included.
> +Support for eMMC, NAND and SPI flash memory devices is included.
>
> Client installation
> -------------------
> @@ -97,8 +97,9 @@ Raw partition descriptors
> ^^^^^^^^^^^^^^^^^^^^^^^^^
>
> In cases where no partition table is present, a raw partition descriptor can be
> -defined, specifying the offset, size, and optionally the MMC hardware partition
> -number for a given partition name.
> +defined, specifying the memory offset and size.
> +
> +Currently, this support is available only for eMMC and SPI flash memory devices.
>
> This is useful when using fastboot to flash files (e.g. SPL or U-Boot) to a
> specific offset in the eMMC boot partition, without having to update the entire
> @@ -106,6 +107,15 @@ boot partition.
>
> To define a raw partition descriptor, add an environment variable similar to::
>
> + fastboot_raw_partition_<raw partition name>=<offset> <size>
> +
> +for example::
> +
> + fastboot_raw_partition_boot=0x100 0x1f00
> +
> +Optionally, in the eMMC case, the hardware partition number can also be
> +specified for a given partition name::
> +
> fastboot_raw_partition_<raw partition name>=<offset> <size> [mmcpart <num>]
>
> for example::
> diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig
> index 70207573de2..843171902ae 100644
> --- a/drivers/fastboot/Kconfig
> +++ b/drivers/fastboot/Kconfig
> @@ -91,7 +91,7 @@ config FASTBOOT_USB_DEV
> config FASTBOOT_FLASH
> bool "Enable FASTBOOT FLASH command"
> default y if ARCH_SUNXI || ARCH_ROCKCHIP
> - depends on MMC || (MTD_RAW_NAND && CMD_MTDPARTS)
> + depends on MMC || (MTD_RAW_NAND && CMD_MTDPARTS) || DM_SPI_FLASH
> select IMAGE_SPARSE
> help
> The fastboot protocol includes a "flash" command for writing
> @@ -119,6 +119,10 @@ config FASTBOOT_FLASH_NAND
> bool "FASTBOOT on NAND"
> depends on MTD_RAW_NAND && CMD_MTDPARTS
>
> +config FASTBOOT_FLASH_SPI
> + bool "FASTBOOT on SPI flash"
> + depends on DM_SPI_FLASH
> +
> endchoice
>
> config FASTBOOT_FLASH_MMC_DEV
> diff --git a/drivers/fastboot/Makefile b/drivers/fastboot/Makefile
> index 048af5aa823..adedba0bf24 100644
> --- a/drivers/fastboot/Makefile
> +++ b/drivers/fastboot/Makefile
> @@ -5,3 +5,4 @@ obj-y += fb_getvar.o
> obj-y += fb_command.o
> obj-$(CONFIG_FASTBOOT_FLASH_MMC) += fb_mmc.o
> obj-$(CONFIG_FASTBOOT_FLASH_NAND) += fb_nand.o
> +obj-$(CONFIG_FASTBOOT_FLASH_SPI) += fb_spi_flash.o
> diff --git a/drivers/fastboot/fb_command.c b/drivers/fastboot/fb_command.c
> index 2cdbac50ac4..7697139b622 100644
> --- a/drivers/fastboot/fb_command.c
> +++ b/drivers/fastboot/fb_command.c
> @@ -10,6 +10,7 @@
> #include <fastboot-internal.h>
> #include <fb_mmc.h>
> #include <fb_nand.h>
> +#include <fb_spi_flash.h>
> #include <part.h>
> #include <stdlib.h>
> #include <vsprintf.h>
> @@ -344,6 +345,10 @@ static void __maybe_unused flash(char *cmd_parameter, char *response)
> if (IS_ENABLED(CONFIG_FASTBOOT_FLASH_NAND))
> fastboot_nand_flash_write(cmd_parameter, fastboot_buf_addr,
> image_size, response);
> +
> + if (IS_ENABLED(CONFIG_FASTBOOT_FLASH_SPI))
> + fastboot_spi_flash_write(cmd_parameter, fastboot_buf_addr,
> + image_size, response);
> }
>
> /**
> @@ -362,6 +367,9 @@ static void __maybe_unused erase(char *cmd_parameter, char *response)
>
> if (IS_ENABLED(CONFIG_FASTBOOT_FLASH_NAND))
> fastboot_nand_erase(cmd_parameter, response);
> +
> + if (IS_ENABLED(CONFIG_FASTBOOT_FLASH_SPI))
> + fastboot_spi_flash_erase(cmd_parameter, response);
> }
>
> /**
> diff --git a/drivers/fastboot/fb_getvar.c b/drivers/fastboot/fb_getvar.c
> index 9c2ce65a4e5..6775ea397ab 100644
> --- a/drivers/fastboot/fb_getvar.c
> +++ b/drivers/fastboot/fb_getvar.c
> @@ -8,6 +8,7 @@
> #include <fastboot-internal.h>
> #include <fb_mmc.h>
> #include <fb_nand.h>
> +#include <fb_spi_flash.h>
> #include <fs.h>
> #include <part.h>
> #include <version.h>
> @@ -123,6 +124,11 @@ static int getvar_get_part_info(const char *part_name, char *response,
> r = fastboot_nand_get_part_info(part_name, &part_info, response);
> if (r >= 0 && size)
> *size = part_info->size;
> + } else if (IS_ENABLED(CONFIG_FASTBOOT_FLASH_SPI)) {
> + r = fastboot_spi_flash_get_part_info(part_name, &disk_part,
> + response);
> + if (r >= 0 && size)
> + *size = disk_part.size * disk_part.blksz;
> } else {
> fastboot_fail("this storage is not supported in bootloader", response);
> r = -ENODEV;
> diff --git a/drivers/fastboot/fb_spi_flash.c b/drivers/fastboot/fb_spi_flash.c
> new file mode 100644
> index 00000000000..691be7c7ef7
> --- /dev/null
> +++ b/drivers/fastboot/fb_spi_flash.c
> @@ -0,0 +1,251 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2025 Collabora Ltd.
> + */
> +
> +#include <blk.h>
> +#include <config.h>
> +#include <env.h>
> +#include <fastboot.h>
> +#include <image-sparse.h>
> +#include <spi.h>
> +#include <spi_flash.h>
> +#include <dm.h>
> +#include <dm/device-internal.h>
> +
> +static struct spi_flash *flash;
> +
> +__weak int board_fastboot_spi_flash_write_setup(void)
> +{
> + return 0;
> +}
> +
> +__weak int board_fastboot_spi_flash_erase_setup(void)
> +{
> + return 0;
> +}
> +
> +static int raw_part_get_info_by_name(const char *name,
> + struct disk_partition *part_info)
> +{
> + /* strlen("fastboot_raw_partition_") + PART_NAME_LEN + 1 */
> + char env_desc_name[23 + PART_NAME_LEN + 1];
> + char *raw_part_desc;
> + const char *argv[2];
> + const char **parg = argv;
> +
> + /* check for raw partition descriptor */
> + strcpy(env_desc_name, "fastboot_raw_partition_");
> + strlcat(env_desc_name, name, sizeof(env_desc_name));
> + raw_part_desc = strdup(env_get(env_desc_name));
> + if (!raw_part_desc)
> + return -ENODEV;
> +
> + /* parse partition descriptor: <start> <size> */
> + for (; parg < argv + sizeof(argv) / sizeof(*argv); ++parg) {
> + *parg = strsep(&raw_part_desc, " ");
> + if (!*parg) {
> + pr_err("Invalid number of arguments.\n");
> + return -ENODEV;
> + }
> + }
> +
> + part_info->start = simple_strtoul(argv[0], NULL, 0);
> + part_info->size = simple_strtoul(argv[1], NULL, 0);
> + strlcpy((char *)part_info->name, name, PART_NAME_LEN);
> +
> + return 0;
> +}
> +
> +static int fastboot_spi_flash_probe(void)
> +{
> + unsigned int bus = CONFIG_SF_DEFAULT_BUS;
> + unsigned int cs = CONFIG_SF_DEFAULT_CS;
> + struct udevice *new, *bus_dev;
> + int ret;
> +
> + /* Remove the old device, otherwise probe will just be a nop */
> + ret = spi_find_bus_and_cs(bus, cs, &bus_dev, &new);
> + if (!ret)
> + device_remove(new, DM_REMOVE_NORMAL);
> +
> + spi_flash_probe_bus_cs(bus, cs, &new);
> + flash = dev_get_uclass_priv(new);
> + if (!flash) {
> + printf("Failed to initialize SPI flash at %u:%u (error %d)\n",
> + bus, cs, ret);
> + return 1;
> + }
> +
> + return 0;
> +}
> +
> +static int fastboot_spi_flash_unlock(struct spi_flash *flash,
> + struct disk_partition *part_info)
> +{
> + int ret = spi_flash_protect(flash, part_info->start, part_info->size,
> + false);
> +
> + if (ret && ret != -EOPNOTSUPP) {
> + printf("Failed to unlock SPI flash (%d)\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static lbaint_t fb_spi_flash_sparse_write(struct sparse_storage *info,
> + lbaint_t blk, lbaint_t blkcnt,
> + const void *buffer)
> +{
> + size_t len = blkcnt * info->blksz;
> + u32 offset = blk * info->blksz;
> + int ret;
> +
> + ret = spi_flash_erase(flash, offset, ROUND(len, flash->erase_size));
> + if (ret < 0) {
> + printf("Failed to erase sparse chunk (%d)\n", ret);
> + return ret;
> + }
> +
> + ret = spi_flash_write(flash, offset, len, buffer);
> + if (ret < 0) {
> + printf("Failed to write sparse chunk (%d)\n", ret);
> + return ret;
> + }
> +
> + return blkcnt;
> +}
> +
> +static lbaint_t fb_spi_flash_sparse_reserve(struct sparse_storage *info,
> + lbaint_t blk, lbaint_t blkcnt)
> +{
> + return blkcnt;
> +}
> +
> +/**
> + * fastboot_spi_flash_get_part_info() - Lookup SPI partition by name
> + *
> + * @part_name: Named device to lookup
> + * @part_info: Pointer to returned struct disk_partition
> + * @response: Pointer to fastboot response buffer
> + * Return: 0 if OK, -ENOENT if no partition name was given, -ENODEV on invalid
> + * raw partition descriptor
> + */
> +int fastboot_spi_flash_get_part_info(const char *part_name,
> + struct disk_partition *part_info,
> + char *response)
> +{
> + int ret;
> +
> + if (!part_name || !strcmp(part_name, "")) {
> + fastboot_fail("partition not given", response);
> + return -ENOENT;
> + }
> +
> + /* TODO: Support partitions on the device */
> + ret = raw_part_get_info_by_name(part_name, part_info);
> + if (ret < 0)
> + fastboot_fail("invalid partition or device", response);
> +
> + return ret;
> +}
> +
> +/**
> + * fastboot_spi_flash_write() - Write image to SPI for fastboot
> + *
> + * @cmd: Named device to write image to
> + * @download_buffer: Pointer to image data
> + * @download_bytes: Size of image data
> + * @response: Pointer to fastboot response buffer
> + */
> +void fastboot_spi_flash_write(const char *cmd, void *download_buffer,
> + u32 download_bytes, char *response)
> +{
> + struct disk_partition part_info;
> + int ret;
> +
> + if (fastboot_spi_flash_get_part_info(cmd, &part_info, response))
> + return;
> +
> + if (fastboot_spi_flash_probe())
> + return;
> +
> + if (board_fastboot_spi_flash_write_setup())
> + return;
> +
> + if (fastboot_spi_flash_unlock(flash, &part_info))
> + return;
> +
> + if (is_sparse_image(download_buffer)) {
> + struct sparse_storage sparse;
> +
> + sparse.blksz = flash->sector_size;
> + sparse.start = part_info.start / sparse.blksz;
> + sparse.size = part_info.size / sparse.blksz;
> + sparse.write = fb_spi_flash_sparse_write;
> + sparse.reserve = fb_spi_flash_sparse_reserve;
> + sparse.mssg = fastboot_fail;
> +
> + printf("Flashing sparse image at offset " LBAFU "\n",
> + sparse.start);
> +
> + ret = write_sparse_image(&sparse, cmd, download_buffer,
> + response);
> + } else {
> + printf("Flashing raw image at offset " LBAFU "\n",
> + part_info.start);
> +
> + ret = spi_flash_erase(flash, part_info.start,
> + ROUND(download_bytes, flash->erase_size));
> + if (ret < 0) {
> + printf("Failed to erase raw image (%d)\n", ret);
> + return;
> + }
> + ret = spi_flash_write(flash, part_info.start, download_bytes,
> + download_buffer);
> + if (ret < 0) {
> + printf("Failed to write raw image (%d)\n", ret);
> + return;
> + }
> + printf("........ wrote %u bytes\n", download_bytes);
> + }
> +
> + if (ret)
> + fastboot_fail("error writing the image", response);
> + else
> + fastboot_okay(NULL, response);
> +}
> +
> +/**
> + * fastboot_spi_flash_erase() - Erase SPI for fastboot
> + *
> + * @cmd: Named device to erase
> + * @response: Pointer to fastboot response buffer
> + */
> +void fastboot_spi_flash_erase(const char *cmd, char *response)
> +{
> + struct disk_partition part_info;
> + int ret;
> +
> + if (fastboot_spi_flash_get_part_info(cmd, &part_info, response))
> + return;
> +
> + if (fastboot_spi_flash_probe())
> + return;
> +
> + if (board_fastboot_spi_flash_erase_setup())
> + return;
> +
> + if (fastboot_spi_flash_unlock(flash, &part_info))
> + return;
> +
> + ret = spi_flash_erase(flash, part_info.start, part_info.size);
> + if (ret < 0) {
> + pr_err("failed erasing from SPI flash");
> + fastboot_fail("failed erasing from SPI flash", response);
> + return;
> + }
> +
> + fastboot_okay(NULL, response);
> +}
> diff --git a/include/fb_spi_flash.h b/include/fb_spi_flash.h
> new file mode 100644
> index 00000000000..904654748a4
> --- /dev/null
> +++ b/include/fb_spi_flash.h
> @@ -0,0 +1,42 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2025 Collabora Ltd.
> + */
> +
> +#ifndef _FB_SPI_FLASH_H_
> +#define _FB_SPI_FLASH_H_
> +
> +#include <part.h>
> +
> +/**
> + * fastboot_spi_flash_get_part_info() - Lookup SPI flash partition by name
> + *
> + * @part_name: Named device to lookup
> + * @part_info: Pointer to returned struct disk_partition
> + * @response: Pointer to fastboot response buffer
> + * Return: 0 if OK, -ENOENT if no partition name was given, -ENODEV on invalid
> + * raw partition descriptor
> + */
> +int fastboot_spi_flash_get_part_info(const char *part_name,
> + struct disk_partition *part_info,
> + char *response);
> +
> +/**
> + * fastboot_spi_flash_write() - Write image to SPI flash for fastboot
> + *
> + * @cmd: Named device to write image to
> + * @download_buffer: Pointer to image data
> + * @download_bytes: Size of image data
> + * @response: Pointer to fastboot response buffer
> + */
> +void fastboot_spi_flash_write(const char *cmd, void *download_buffer,
> + u32 download_bytes, char *response);
> +
> +/**
> + * fastboot_spi_flash_erase() - Erase SPI flash for fastboot
> + *
> + * @cmd: Named device to erase
> + * @response: Pointer to fastboot response buffer
> + */
> +void fastboot_spi_flash_erase(const char *cmd, char *response);
> +#endif
> --
> 2.50.0
More information about the U-Boot
mailing list