u-boot/drivers/fastboot/fb_mmc.c
Tom Rini 17ead040d4 Audit <flash.h> inclusion
A large number of files include <flash.h> as it used to be how various
SPI flash related functions were found, or for other reasons entirely.
In order to migrate some further CONFIG symbols to Kconfig we need to
not include flash.h in cases where we don't have a NOR flash of some
sort enabled.  Furthermore, in cases where we are in common code and it
doesn't make sense to try and further refactor the code itself in to new
files we need to guard this inclusion.

Signed-off-by: Tom Rini <trini@konsulko.com>
2022-08-04 16:18:47 -04:00

706 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2014 Broadcom Corporation.
*/
#include <config.h>
#include <common.h>
#include <blk.h>
#include <env.h>
#include <fastboot.h>
#include <fastboot-internal.h>
#include <fb_mmc.h>
#include <image-sparse.h>
#include <image.h>
#include <log.h>
#include <part.h>
#include <mmc.h>
#include <div64.h>
#include <linux/compat.h>
#include <android_image.h>
#define FASTBOOT_MAX_BLK_WRITE 16384
#define BOOT_PARTITION_NAME "boot"
struct fb_mmc_sparse {
struct blk_desc *dev_desc;
};
static int raw_part_get_info_by_name(struct blk_desc *dev_desc,
const char *name,
struct disk_partition *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 == NULL)
return -ENODEV;
/*
* parse partition descriptor
*
* <lba_start> <lba_size> [mmcpart <num>]
*/
for (; parg < argv + sizeof(argv) / sizeof(*argv); ++parg) {
*parg = strsep(&raw_part_desc, " ");
if (*parg == NULL) {
pr_err("Invalid number of arguments.\n");
return -ENODEV;
}
}
info->start = simple_strtoul(argv[0], NULL, 0);
info->size = simple_strtoul(argv[1], NULL, 0);
info->blksz = dev_desc->blksz;
strlcpy((char *)info->name, name, PART_NAME_LEN);
if (raw_part_desc) {
if (strcmp(strsep(&raw_part_desc, " "), "mmcpart") == 0) {
ulong mmcpart = simple_strtoul(raw_part_desc, NULL, 0);
int ret = blk_dselect_hwpart(dev_desc, mmcpart);
if (ret)
return ret;
}
}
return 0;
}
static int do_get_part_info(struct blk_desc **dev_desc, const char *name,
struct disk_partition *info)
{
int ret;
/* First try partition names on the default device */
*dev_desc = blk_get_dev("mmc", CONFIG_FASTBOOT_FLASH_MMC_DEV);
if (*dev_desc) {
ret = part_get_info_by_name(*dev_desc, name, info);
if (ret >= 0)
return ret;
/* Then try raw partitions */
ret = raw_part_get_info_by_name(*dev_desc, name, info);
if (ret >= 0)
return ret;
}
/* Then try dev.hwpart:part */
ret = part_get_info_by_dev_and_name_or_num("mmc", name, dev_desc,
info, true);
return ret;
}
static int part_get_info_by_name_or_alias(struct blk_desc **dev_desc,
const char *name,
struct disk_partition *info)
{
/* strlen("fastboot_partition_alias_") + PART_NAME_LEN + 1 */
char env_alias_name[25 + PART_NAME_LEN + 1];
char *aliased_part_name;
/* check for alias */
strlcpy(env_alias_name, "fastboot_partition_alias_", sizeof(env_alias_name));
strlcat(env_alias_name, name, sizeof(env_alias_name));
aliased_part_name = env_get(env_alias_name);
if (aliased_part_name)
name = aliased_part_name;
return do_get_part_info(dev_desc, name, info);
}
/**
* fb_mmc_blk_write() - Write/erase MMC in chunks of FASTBOOT_MAX_BLK_WRITE
*
* @block_dev: Pointer to block device
* @start: First block to write/erase
* @blkcnt: Count of blocks
* @buffer: Pointer to data buffer for write or NULL for erase
*/
static lbaint_t fb_mmc_blk_write(struct blk_desc *block_dev, lbaint_t start,
lbaint_t blkcnt, const void *buffer)
{
lbaint_t blk = start;
lbaint_t blks_written;
lbaint_t cur_blkcnt;
lbaint_t blks = 0;
int i;
for (i = 0; i < blkcnt; i += FASTBOOT_MAX_BLK_WRITE) {
cur_blkcnt = min((int)blkcnt - i, FASTBOOT_MAX_BLK_WRITE);
if (buffer) {
if (fastboot_progress_callback)
fastboot_progress_callback("writing");
blks_written = blk_dwrite(block_dev, blk, cur_blkcnt,
buffer + (i * block_dev->blksz));
} else {
if (fastboot_progress_callback)
fastboot_progress_callback("erasing");
blks_written = blk_derase(block_dev, blk, cur_blkcnt);
}
blk += blks_written;
blks += blks_written;
}
return blks;
}
static lbaint_t fb_mmc_sparse_write(struct sparse_storage *info,
lbaint_t blk, lbaint_t blkcnt, const void *buffer)
{
struct fb_mmc_sparse *sparse = info->priv;
struct blk_desc *dev_desc = sparse->dev_desc;
return fb_mmc_blk_write(dev_desc, blk, blkcnt, buffer);
}
static lbaint_t fb_mmc_sparse_reserve(struct sparse_storage *info,
lbaint_t blk, lbaint_t blkcnt)
{
return blkcnt;
}
static void write_raw_image(struct blk_desc *dev_desc,
struct disk_partition *info, const char *part_name,
void *buffer, u32 download_bytes, char *response)
{
lbaint_t blkcnt;
lbaint_t blks;
/* determine number of blocks to write */
blkcnt = ((download_bytes + (info->blksz - 1)) & ~(info->blksz - 1));
blkcnt = lldiv(blkcnt, info->blksz);
if (blkcnt > info->size) {
pr_err("too large for partition: '%s'\n", part_name);
fastboot_fail("too large for partition", response);
return;
}
puts("Flashing Raw Image\n");
blks = fb_mmc_blk_write(dev_desc, info->start, blkcnt, buffer);
if (blks != blkcnt) {
pr_err("failed writing to device %d\n", dev_desc->devnum);
fastboot_fail("failed writing to device", response);
return;
}
printf("........ wrote " LBAFU " bytes to '%s'\n", blkcnt * info->blksz,
part_name);
fastboot_okay(NULL, response);
}
#if defined(CONFIG_FASTBOOT_MMC_BOOT_SUPPORT) || \
defined(CONFIG_FASTBOOT_MMC_USER_SUPPORT)
static int fb_mmc_erase_mmc_hwpart(struct blk_desc *dev_desc)
{
lbaint_t blks;
debug("Start Erasing mmc hwpart[%u]...\n", dev_desc->hwpart);
blks = fb_mmc_blk_write(dev_desc, 0, dev_desc->lba, NULL);
if (blks != dev_desc->lba) {
pr_err("Failed to erase mmc hwpart[%u]\n", dev_desc->hwpart);
return 1;
}
printf("........ erased %lu bytes from mmc hwpart[%u]\n",
dev_desc->lba * dev_desc->blksz, dev_desc->hwpart);
return 0;
}
#endif
#ifdef CONFIG_FASTBOOT_MMC_BOOT_SUPPORT
static void fb_mmc_boot_ops(struct blk_desc *dev_desc, void *buffer,
int hwpart, u32 buff_sz, char *response)
{
lbaint_t blkcnt;
lbaint_t blks;
unsigned long blksz;
// To operate on EMMC_BOOT1/2 (mmc0boot0/1) we first change the hwpart
if (blk_dselect_hwpart(dev_desc, hwpart)) {
pr_err("Failed to select hwpart\n");
fastboot_fail("Failed to select hwpart", response);
return;
}
if (buffer) { /* flash */
/* determine number of blocks to write */
blksz = dev_desc->blksz;
blkcnt = ((buff_sz + (blksz - 1)) & ~(blksz - 1));
blkcnt = lldiv(blkcnt, blksz);
if (blkcnt > dev_desc->lba) {
pr_err("Image size too large\n");
fastboot_fail("Image size too large", response);
return;
}
debug("Start Flashing Image to EMMC_BOOT%d...\n", hwpart);
blks = fb_mmc_blk_write(dev_desc, 0, blkcnt, buffer);
if (blks != blkcnt) {
pr_err("Failed to write EMMC_BOOT%d\n", hwpart);
fastboot_fail("Failed to write EMMC_BOOT part",
response);
return;
}
printf("........ wrote %lu bytes to EMMC_BOOT%d\n",
blkcnt * blksz, hwpart);
} else { /* erase */
if (fb_mmc_erase_mmc_hwpart(dev_desc)) {
pr_err("Failed to erase EMMC_BOOT%d\n", hwpart);
fastboot_fail("Failed to erase EMMC_BOOT part",
response);
return;
}
}
fastboot_okay(NULL, response);
}
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
/**
* Read Android boot image header from boot partition.
*
* @param[in] dev_desc MMC device descriptor
* @param[in] info Boot partition info
* @param[out] hdr Where to store read boot image header
*
* Return: Boot image header sectors count or 0 on error
*/
static lbaint_t fb_mmc_get_boot_header(struct blk_desc *dev_desc,
struct disk_partition *info,
struct andr_img_hdr *hdr,
char *response)
{
ulong sector_size; /* boot partition sector size */
lbaint_t hdr_sectors; /* boot image header sectors count */
int res;
/* Calculate boot image sectors count */
sector_size = info->blksz;
hdr_sectors = DIV_ROUND_UP(sizeof(struct andr_img_hdr), sector_size);
if (hdr_sectors == 0) {
pr_err("invalid number of boot sectors: 0\n");
fastboot_fail("invalid number of boot sectors: 0", response);
return 0;
}
/* Read the boot image header */
res = blk_dread(dev_desc, info->start, hdr_sectors, (void *)hdr);
if (res != hdr_sectors) {
pr_err("cannot read header from boot partition\n");
fastboot_fail("cannot read header from boot partition",
response);
return 0;
}
/* Check boot header magic string */
res = android_image_check_header(hdr);
if (res != 0) {
pr_err("bad boot image magic\n");
fastboot_fail("boot partition not initialized", response);
return 0;
}
return hdr_sectors;
}
/**
* Write downloaded zImage to boot partition and repack it properly.
*
* @param dev_desc MMC device descriptor
* @param download_buffer Address to fastboot buffer with zImage in it
* @param download_bytes Size of fastboot buffer, in bytes
*
* Return: 0 on success or -1 on error
*/
static int fb_mmc_update_zimage(struct blk_desc *dev_desc,
void *download_buffer,
u32 download_bytes,
char *response)
{
uintptr_t hdr_addr; /* boot image header address */
struct andr_img_hdr *hdr; /* boot image header */
lbaint_t hdr_sectors; /* boot image header sectors */
u8 *ramdisk_buffer;
u32 ramdisk_sector_start;
u32 ramdisk_sectors;
u32 kernel_sector_start;
u32 kernel_sectors;
u32 sectors_per_page;
struct disk_partition info;
int res;
puts("Flashing zImage\n");
/* Get boot partition info */
res = part_get_info_by_name(dev_desc, BOOT_PARTITION_NAME, &info);
if (res < 0) {
pr_err("cannot find boot partition\n");
fastboot_fail("cannot find boot partition", response);
return -1;
}
/* Put boot image header in fastboot buffer after downloaded zImage */
hdr_addr = (uintptr_t)download_buffer + ALIGN(download_bytes, PAGE_SIZE);
hdr = (struct andr_img_hdr *)hdr_addr;
/* Read boot image header */
hdr_sectors = fb_mmc_get_boot_header(dev_desc, &info, hdr, response);
if (hdr_sectors == 0) {
pr_err("unable to read boot image header\n");
fastboot_fail("unable to read boot image header", response);
return -1;
}
/* Check if boot image has second stage in it (we don't support it) */
if (hdr->second_size > 0) {
pr_err("moving second stage is not supported yet\n");
fastboot_fail("moving second stage is not supported yet",
response);
return -1;
}
/* Extract ramdisk location */
sectors_per_page = hdr->page_size / info.blksz;
ramdisk_sector_start = info.start + sectors_per_page;
ramdisk_sector_start += DIV_ROUND_UP(hdr->kernel_size, hdr->page_size) *
sectors_per_page;
ramdisk_sectors = DIV_ROUND_UP(hdr->ramdisk_size, hdr->page_size) *
sectors_per_page;
/* Read ramdisk and put it in fastboot buffer after boot image header */
ramdisk_buffer = (u8 *)hdr + (hdr_sectors * info.blksz);
res = blk_dread(dev_desc, ramdisk_sector_start, ramdisk_sectors,
ramdisk_buffer);
if (res != ramdisk_sectors) {
pr_err("cannot read ramdisk from boot partition\n");
fastboot_fail("cannot read ramdisk from boot partition",
response);
return -1;
}
/* Write new kernel size to boot image header */
hdr->kernel_size = download_bytes;
res = blk_dwrite(dev_desc, info.start, hdr_sectors, (void *)hdr);
if (res == 0) {
pr_err("cannot writeback boot image header\n");
fastboot_fail("cannot write back boot image header", response);
return -1;
}
/* Write the new downloaded kernel */
kernel_sector_start = info.start + sectors_per_page;
kernel_sectors = DIV_ROUND_UP(hdr->kernel_size, hdr->page_size) *
sectors_per_page;
res = blk_dwrite(dev_desc, kernel_sector_start, kernel_sectors,
download_buffer);
if (res == 0) {
pr_err("cannot write new kernel\n");
fastboot_fail("cannot write new kernel", response);
return -1;
}
/* Write the saved ramdisk back */
ramdisk_sector_start = info.start + sectors_per_page;
ramdisk_sector_start += DIV_ROUND_UP(hdr->kernel_size, hdr->page_size) *
sectors_per_page;
res = blk_dwrite(dev_desc, ramdisk_sector_start, ramdisk_sectors,
ramdisk_buffer);
if (res == 0) {
pr_err("cannot write back original ramdisk\n");
fastboot_fail("cannot write back original ramdisk", response);
return -1;
}
puts("........ zImage was updated in boot partition\n");
fastboot_okay(NULL, response);
return 0;
}
#endif
/**
* fastboot_mmc_get_part_info() - Lookup eMMC partion by name
*
* @part_name: Named partition to lookup
* @dev_desc: Pointer to returned blk_desc pointer
* @part_info: Pointer to returned struct disk_partition
* @response: Pointer to fastboot response buffer
*/
int fastboot_mmc_get_part_info(const char *part_name,
struct blk_desc **dev_desc,
struct disk_partition *part_info, char *response)
{
int ret;
if (!part_name || !strcmp(part_name, "")) {
fastboot_fail("partition not given", response);
return -ENOENT;
}
ret = part_get_info_by_name_or_alias(dev_desc, part_name, part_info);
if (ret < 0) {
switch (ret) {
case -ENOSYS:
case -EINVAL:
fastboot_fail("invalid partition or device", response);
break;
case -ENODEV:
fastboot_fail("no such device", response);
break;
case -ENOENT:
fastboot_fail("no such partition", response);
break;
case -EPROTONOSUPPORT:
fastboot_fail("unknown partition table type", response);
break;
default:
fastboot_fail("unanticipated error", response);
break;
}
}
return ret;
}
static struct blk_desc *fastboot_mmc_get_dev(char *response)
{
struct blk_desc *ret = blk_get_dev("mmc",
CONFIG_FASTBOOT_FLASH_MMC_DEV);
if (!ret || ret->type == DEV_TYPE_UNKNOWN) {
pr_err("invalid mmc device\n");
fastboot_fail("invalid mmc device", response);
return NULL;
}
return ret;
}
/**
* fastboot_mmc_flash_write() - Write image to eMMC for fastboot
*
* @cmd: Named partition to write image to
* @download_buffer: Pointer to image data
* @download_bytes: Size of image data
* @response: Pointer to fastboot response buffer
*/
void fastboot_mmc_flash_write(const char *cmd, void *download_buffer,
u32 download_bytes, char *response)
{
struct blk_desc *dev_desc;
struct disk_partition info = {0};
#ifdef CONFIG_FASTBOOT_MMC_BOOT_SUPPORT
if (strcmp(cmd, CONFIG_FASTBOOT_MMC_BOOT1_NAME) == 0) {
dev_desc = fastboot_mmc_get_dev(response);
if (dev_desc)
fb_mmc_boot_ops(dev_desc, download_buffer, 1,
download_bytes, response);
return;
}
if (strcmp(cmd, CONFIG_FASTBOOT_MMC_BOOT2_NAME) == 0) {
dev_desc = fastboot_mmc_get_dev(response);
if (dev_desc)
fb_mmc_boot_ops(dev_desc, download_buffer, 2,
download_bytes, response);
return;
}
#endif
#if CONFIG_IS_ENABLED(EFI_PARTITION)
if (strcmp(cmd, CONFIG_FASTBOOT_GPT_NAME) == 0) {
dev_desc = fastboot_mmc_get_dev(response);
if (!dev_desc)
return;
printf("%s: updating MBR, Primary and Backup GPT(s)\n",
__func__);
if (is_valid_gpt_buf(dev_desc, download_buffer)) {
printf("%s: invalid GPT - refusing to write to flash\n",
__func__);
fastboot_fail("invalid GPT partition", response);
return;
}
if (write_mbr_and_gpt_partitions(dev_desc, download_buffer)) {
printf("%s: writing GPT partitions failed\n", __func__);
fastboot_fail("writing GPT partitions failed",
response);
return;
}
part_init(dev_desc);
printf("........ success\n");
fastboot_okay(NULL, response);
return;
}
#endif
#if CONFIG_IS_ENABLED(DOS_PARTITION)
if (strcmp(cmd, CONFIG_FASTBOOT_MBR_NAME) == 0) {
dev_desc = fastboot_mmc_get_dev(response);
if (!dev_desc)
return;
printf("%s: updating MBR\n", __func__);
if (is_valid_dos_buf(download_buffer)) {
printf("%s: invalid MBR - refusing to write to flash\n",
__func__);
fastboot_fail("invalid MBR partition", response);
return;
}
if (write_mbr_sector(dev_desc, download_buffer)) {
printf("%s: writing MBR partition failed\n", __func__);
fastboot_fail("writing MBR partition failed",
response);
return;
}
part_init(dev_desc);
printf("........ success\n");
fastboot_okay(NULL, response);
return;
}
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
if (strncasecmp(cmd, "zimage", 6) == 0) {
dev_desc = fastboot_mmc_get_dev(response);
if (dev_desc)
fb_mmc_update_zimage(dev_desc, download_buffer,
download_bytes, response);
return;
}
#endif
#if CONFIG_IS_ENABLED(FASTBOOT_MMC_USER_SUPPORT)
if (strcmp(cmd, CONFIG_FASTBOOT_MMC_USER_NAME) == 0) {
dev_desc = fastboot_mmc_get_dev(response);
if (!dev_desc)
return;
strlcpy((char *)&info.name, cmd, sizeof(info.name));
info.size = dev_desc->lba;
info.blksz = dev_desc->blksz;
}
#endif
if (!info.name[0] &&
fastboot_mmc_get_part_info(cmd, &dev_desc, &info, response) < 0)
return;
if (is_sparse_image(download_buffer)) {
struct fb_mmc_sparse sparse_priv;
struct sparse_storage sparse;
int err;
sparse_priv.dev_desc = dev_desc;
sparse.blksz = info.blksz;
sparse.start = info.start;
sparse.size = info.size;
sparse.write = fb_mmc_sparse_write;
sparse.reserve = fb_mmc_sparse_reserve;
sparse.mssg = fastboot_fail;
printf("Flashing sparse image at offset " LBAFU "\n",
sparse.start);
sparse.priv = &sparse_priv;
err = write_sparse_image(&sparse, cmd, download_buffer,
response);
if (!err)
fastboot_okay(NULL, response);
} else {
write_raw_image(dev_desc, &info, cmd, download_buffer,
download_bytes, response);
}
}
/**
* fastboot_mmc_flash_erase() - Erase eMMC for fastboot
*
* @cmd: Named partition to erase
* @response: Pointer to fastboot response buffer
*/
void fastboot_mmc_erase(const char *cmd, char *response)
{
struct blk_desc *dev_desc;
struct disk_partition info;
lbaint_t blks, blks_start, blks_size, grp_size;
struct mmc *mmc = find_mmc_device(CONFIG_FASTBOOT_FLASH_MMC_DEV);
#ifdef CONFIG_FASTBOOT_MMC_BOOT_SUPPORT
if (strcmp(cmd, CONFIG_FASTBOOT_MMC_BOOT1_NAME) == 0) {
/* erase EMMC boot1 */
dev_desc = fastboot_mmc_get_dev(response);
if (dev_desc)
fb_mmc_boot_ops(dev_desc, NULL, 1, 0, response);
return;
}
if (strcmp(cmd, CONFIG_FASTBOOT_MMC_BOOT2_NAME) == 0) {
/* erase EMMC boot2 */
dev_desc = fastboot_mmc_get_dev(response);
if (dev_desc)
fb_mmc_boot_ops(dev_desc, NULL, 2, 0, response);
return;
}
#endif
#ifdef CONFIG_FASTBOOT_MMC_USER_SUPPORT
if (strcmp(cmd, CONFIG_FASTBOOT_MMC_USER_NAME) == 0) {
/* erase EMMC userdata */
dev_desc = fastboot_mmc_get_dev(response);
if (!dev_desc)
return;
if (fb_mmc_erase_mmc_hwpart(dev_desc))
fastboot_fail("Failed to erase EMMC_USER", response);
else
fastboot_okay(NULL, response);
return;
}
#endif
if (fastboot_mmc_get_part_info(cmd, &dev_desc, &info, response) < 0)
return;
/* Align blocks to erase group size to avoid erasing other partitions */
grp_size = mmc->erase_grp_size;
blks_start = (info.start + grp_size - 1) & ~(grp_size - 1);
if (info.size >= grp_size)
blks_size = (info.size - (blks_start - info.start)) &
(~(grp_size - 1));
else
blks_size = 0;
printf("Erasing blocks " LBAFU " to " LBAFU " due to alignment\n",
blks_start, blks_start + blks_size);
blks = fb_mmc_blk_write(dev_desc, blks_start, blks_size, NULL);
if (blks != blks_size) {
pr_err("failed erasing from device %d\n", dev_desc->devnum);
fastboot_fail("failed erasing from device", response);
return;
}
printf("........ erased " LBAFU " bytes from '%s'\n",
blks_size * info.blksz, cmd);
fastboot_okay(NULL, response);
}