diff options
Diffstat (limited to 'drivers/fastboot/fb_spi_flash.c')
-rw-r--r-- | drivers/fastboot/fb_spi_flash.c | 251 |
1 files changed, 251 insertions, 0 deletions
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); +} |