u-boot/env/mmc.c
Rasmus Villemoes c1131aca9a env: mmc: rework mmc_env_partition_by_guid() to work with two separate partitions
Having two separate partitions for use in a redundant environment
setup works just fine, if one only relies on setting CONFIG_ENV_OFFSET
and CONFIG_ENV_OFFSET_REDUND. However, if CONFIG_PARTITION_TYPE_GUID
is enabled, the current logic in mmc_env_partition_by_guid() means
that only the first partition will ever be considered, and prior to
the previous commit, lead to silent data corruption.

Extend the logic so that, when we are looking for the location for the
second copy of the environment, we keep track of whether we have
already found one matching partition. If a second match is found,
return that, but also modify *copy so that the logic in the caller
will use the last ENV_SIZE bytes of that second partition - in my
case, and I suppose that would be typical, both partitions have been
created with a size of exactly the desired ENV_SIZE.

When only a single matching partition exists, the behaviour is
unchanged: We return that single partition, and *copy is left as-is,
so the logic in the caller will either use the last (copy==0) or
second-to-last (copy==1) ENV_SIZE bytes.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
2024-10-01 08:44:39 -06:00

565 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2008-2011 Freescale Semiconductor, Inc.
*/
/* #define DEBUG */
#include <asm/global_data.h>
#include <command.h>
#include <env.h>
#include <env_internal.h>
#include <fdtdec.h>
#include <linux/stddef.h>
#include <malloc.h>
#include <memalign.h>
#include <mmc.h>
#include <part.h>
#include <search.h>
#include <errno.h>
#include <dm/ofnode.h>
#define ENV_MMC_INVALID_OFFSET ((s64)-1)
#if defined(CONFIG_ENV_MMC_USE_DT)
/* ENV offset is invalid when not defined in Device Tree */
#define ENV_MMC_OFFSET ENV_MMC_INVALID_OFFSET
#define ENV_MMC_OFFSET_REDUND ENV_MMC_INVALID_OFFSET
#else
/* Default ENV offset when not defined in Device Tree */
#define ENV_MMC_OFFSET CONFIG_ENV_OFFSET
#if defined(CONFIG_ENV_OFFSET_REDUND)
#define ENV_MMC_OFFSET_REDUND CONFIG_ENV_OFFSET_REDUND
#else
#define ENV_MMC_OFFSET_REDUND ENV_MMC_INVALID_OFFSET
#endif
#endif
DECLARE_GLOBAL_DATA_PTR;
/*
* In case the environment is redundant, stored in eMMC hardware boot
* partition and the environment and redundant environment offsets are
* identical, store the environment and redundant environment in both
* eMMC boot partitions, one copy in each.
* */
#if (defined(CONFIG_SYS_REDUNDAND_ENVIRONMENT) && \
(CONFIG_SYS_MMC_ENV_PART == 1) && \
(CONFIG_ENV_OFFSET == CONFIG_ENV_OFFSET_REDUND))
#define ENV_MMC_HWPART_REDUND 1
#endif
#if CONFIG_IS_ENABLED(OF_CONTROL)
static int mmc_env_partition_by_name(struct blk_desc *desc, const char *str,
struct disk_partition *info)
{
int i, ret;
for (i = 1;; i++) {
ret = part_get_info(desc, i, info);
if (ret < 0)
return ret;
if (!strncmp((const char *)info->name, str, sizeof(info->name)))
return 0;
}
}
/*
* Look for one or two partitions with the U-Boot environment GUID.
*
* If *copy is 0, return the first such partition.
*
* If *copy is 1 on entry and two partitions are found, return the
* second partition and set *copy = 0.
*
* If *copy is 1 on entry and only one partition is found, return that
* partition, leaving *copy unmodified.
*/
static int mmc_env_partition_by_guid(struct blk_desc *desc, struct disk_partition *info,
int *copy)
{
const efi_guid_t env_guid = PARTITION_U_BOOT_ENVIRONMENT;
efi_guid_t type_guid;
int i, ret, found = 0;
struct disk_partition dp;
for (i = 1;; i++) {
ret = part_get_info(desc, i, &dp);
if (ret < 0)
break;
uuid_str_to_bin(disk_partition_type_guid(&dp), type_guid.b, UUID_STR_FORMAT_GUID);
if (!memcmp(&env_guid, &type_guid, sizeof(efi_guid_t))) {
memcpy(info, &dp, sizeof(dp));
/* If *copy is 0, we are only looking for the first partition. */
if (*copy == 0)
return 0;
/* This was the second such partition. */
if (found) {
*copy = 0;
return 0;
}
found = 1;
}
}
/* The loop ended after finding at most one matching partition. */
if (found)
ret = 0;
return ret;
}
static inline int mmc_offset_try_partition(const char *str, int copy, s64 *val)
{
struct disk_partition info;
struct blk_desc *desc;
lbaint_t len;
int ret;
char dev_str[4];
snprintf(dev_str, sizeof(dev_str), "%d", mmc_get_env_dev());
ret = blk_get_device_by_str("mmc", dev_str, &desc);
if (ret < 0)
return (ret);
if (str) {
ret = mmc_env_partition_by_name(desc, str, &info);
} else if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID) && !str) {
ret = mmc_env_partition_by_guid(desc, &info, &copy);
}
if (ret < 0)
return ret;
/* round up to info.blksz */
len = DIV_ROUND_UP(CONFIG_ENV_SIZE, info.blksz);
if ((1 + copy) * len > info.size) {
printf("Partition '%s' [0x"LBAF"; 0x"LBAF"] too small for %senvironment, required size 0x"LBAF" blocks\n",
(const char*)info.name, info.start, info.size,
copy ? "two copies of " : "", (1 + copy)*len);
return -ENOSPC;
}
/* use the top of the partion for the environment */
*val = (info.start + info.size - (1 + copy) * len) * info.blksz;
return 0;
}
static inline s64 mmc_offset(struct mmc *mmc, int copy)
{
const struct {
const char *offset_redund;
const char *partition;
const char *offset;
} dt_prop = {
.offset_redund = "u-boot,mmc-env-offset-redundant",
.partition = "u-boot,mmc-env-partition",
.offset = "u-boot,mmc-env-offset",
};
s64 val = 0, defvalue;
const char *propname;
const char *str;
int hwpart = 0;
int err;
#if defined(CONFIG_SYS_MMC_ENV_PART)
hwpart = mmc_get_env_part(mmc);
#endif
#if defined(CONFIG_ENV_MMC_PARTITION)
str = CONFIG_ENV_MMC_PARTITION;
#else
/* look for the partition in mmc CONFIG_SYS_MMC_ENV_DEV */
str = ofnode_conf_read_str(dt_prop.partition);
#endif
if (str) {
/* try to place the environment at end of the partition */
err = mmc_offset_try_partition(str, copy, &val);
if (!err)
return val;
debug("env partition '%s' not found (%d)", str, err);
}
/* try the GPT partition with "U-Boot ENV" TYPE GUID */
if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID) && hwpart == 0) {
err = mmc_offset_try_partition(NULL, copy, &val);
if (!err)
return val;
}
defvalue = ENV_MMC_OFFSET;
propname = dt_prop.offset;
if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT) && copy) {
defvalue = ENV_MMC_OFFSET_REDUND;
propname = dt_prop.offset_redund;
}
return ofnode_conf_read_int(propname, defvalue);
}
#else
static inline s64 mmc_offset(struct mmc *mmc, int copy)
{
s64 offset = ENV_MMC_OFFSET;
if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT) && copy)
offset = ENV_MMC_OFFSET_REDUND;
return offset;
}
#endif
__weak int mmc_get_env_addr(struct mmc *mmc, int copy, u32 *env_addr)
{
s64 offset = mmc_offset(mmc, copy);
if (offset == ENV_MMC_INVALID_OFFSET) {
printf("Invalid ENV offset in MMC, copy=%d\n", copy);
return -ENOENT;
}
if (offset < 0)
offset += mmc->capacity;
*env_addr = offset;
return 0;
}
#ifdef CONFIG_SYS_MMC_ENV_PART
__weak uint mmc_get_env_part(struct mmc *mmc)
{
return CONFIG_SYS_MMC_ENV_PART;
}
static unsigned char env_mmc_orig_hwpart;
static int mmc_set_env_part(struct mmc *mmc, uint part)
{
int dev = mmc_get_env_dev();
int ret = 0;
ret = blk_select_hwpart_devnum(UCLASS_MMC, dev, part);
if (ret)
puts("MMC partition switch failed\n");
return ret;
}
static bool mmc_set_env_part_init(struct mmc *mmc)
{
env_mmc_orig_hwpart = mmc_get_blk_desc(mmc)->hwpart;
if (mmc_set_env_part(mmc, mmc_get_env_part(mmc)))
return false;
return true;
}
static int mmc_set_env_part_restore(struct mmc *mmc)
{
return mmc_set_env_part(mmc, env_mmc_orig_hwpart);
}
#else
static inline int mmc_set_env_part(struct mmc *mmc, uint part) {return 0; };
static bool mmc_set_env_part_init(struct mmc *mmc) {return true; }
static inline int mmc_set_env_part_restore(struct mmc *mmc) {return 0; };
#endif
static const char *init_mmc_for_env(struct mmc *mmc)
{
if (!mmc)
return "No MMC card found";
#if CONFIG_IS_ENABLED(BLK)
struct udevice *dev;
if (blk_get_from_parent(mmc->dev, &dev))
return "No block device";
#else
if (mmc_init(mmc))
return "MMC init failed";
#endif
if (!mmc_set_env_part_init(mmc))
return "MMC partition switch failed";
return NULL;
}
static void fini_mmc_for_env(struct mmc *mmc)
{
mmc_set_env_part_restore(mmc);
}
#if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_SPL_BUILD)
static inline int write_env(struct mmc *mmc, unsigned long size,
unsigned long offset, const void *buffer)
{
uint blk_start, blk_cnt, n;
struct blk_desc *desc = mmc_get_blk_desc(mmc);
blk_start = ALIGN(offset, mmc->write_bl_len) / mmc->write_bl_len;
blk_cnt = ALIGN(size, mmc->write_bl_len) / mmc->write_bl_len;
n = blk_dwrite(desc, blk_start, blk_cnt, (u_char *)buffer);
return (n == blk_cnt) ? 0 : -1;
}
static int env_mmc_save(void)
{
ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
int dev = mmc_get_env_dev();
struct mmc *mmc = find_mmc_device(dev);
u32 offset;
int ret, copy = 0;
const char *errmsg;
errmsg = init_mmc_for_env(mmc);
if (errmsg) {
printf("%s\n", errmsg);
return 1;
}
ret = env_export(env_new);
if (ret)
goto fini;
if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT)) {
if (gd->env_valid == ENV_VALID)
copy = 1;
if (IS_ENABLED(ENV_MMC_HWPART_REDUND)) {
ret = mmc_set_env_part(mmc, copy + 1);
if (ret)
goto fini;
}
}
if (mmc_get_env_addr(mmc, copy, &offset)) {
ret = 1;
goto fini;
}
printf("Writing to %sMMC(%d)... ", copy ? "redundant " : "", dev);
if (write_env(mmc, CONFIG_ENV_SIZE, offset, (u_char *)env_new)) {
puts("failed\n");
ret = 1;
goto fini;
}
ret = 0;
if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT))
gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID : ENV_REDUND;
fini:
fini_mmc_for_env(mmc);
return ret;
}
static inline int erase_env(struct mmc *mmc, unsigned long size,
unsigned long offset)
{
uint blk_start, blk_cnt, n;
struct blk_desc *desc = mmc_get_blk_desc(mmc);
u32 erase_size;
erase_size = mmc->erase_grp_size * desc->blksz;
blk_start = ALIGN_DOWN(offset, erase_size) / desc->blksz;
blk_cnt = ALIGN(size, erase_size) / desc->blksz;
n = blk_derase(desc, blk_start, blk_cnt);
printf("%d blocks erased at 0x%x: %s\n", n, blk_start,
(n == blk_cnt) ? "OK" : "ERROR");
return (n == blk_cnt) ? 0 : 1;
}
static int env_mmc_erase(void)
{
int dev = mmc_get_env_dev();
struct mmc *mmc = find_mmc_device(dev);
int ret, copy = 0;
u32 offset;
const char *errmsg;
errmsg = init_mmc_for_env(mmc);
if (errmsg) {
printf("%s\n", errmsg);
return 1;
}
if (mmc_get_env_addr(mmc, copy, &offset)) {
ret = CMD_RET_FAILURE;
goto fini;
}
printf("\n");
ret = erase_env(mmc, CONFIG_ENV_SIZE, offset);
if (IS_ENABLED(CONFIG_SYS_REDUNDAND_ENVIRONMENT)) {
copy = 1;
if (IS_ENABLED(ENV_MMC_HWPART_REDUND)) {
ret = mmc_set_env_part(mmc, copy + 1);
if (ret)
goto fini;
}
if (mmc_get_env_addr(mmc, copy, &offset)) {
ret = CMD_RET_FAILURE;
goto fini;
}
ret |= erase_env(mmc, CONFIG_ENV_SIZE, offset);
}
fini:
fini_mmc_for_env(mmc);
return ret;
}
#endif /* CONFIG_CMD_SAVEENV && !CONFIG_SPL_BUILD */
static inline int read_env(struct mmc *mmc, unsigned long size,
unsigned long offset, const void *buffer)
{
uint blk_start, blk_cnt, n;
struct blk_desc *desc = mmc_get_blk_desc(mmc);
blk_start = ALIGN(offset, mmc->read_bl_len) / mmc->read_bl_len;
blk_cnt = ALIGN(size, mmc->read_bl_len) / mmc->read_bl_len;
n = blk_dread(desc, blk_start, blk_cnt, (uchar *)buffer);
return (n == blk_cnt) ? 0 : -1;
}
#if defined(ENV_IS_EMBEDDED)
static int env_mmc_load(void)
{
return 0;
}
#elif defined(CONFIG_SYS_REDUNDAND_ENVIRONMENT)
static int env_mmc_load(void)
{
struct mmc *mmc;
u32 offset1, offset2;
int read1_fail = 0, read2_fail = 0;
int ret;
int dev = mmc_get_env_dev();
const char *errmsg = NULL;
ALLOC_CACHE_ALIGN_BUFFER(env_t, tmp_env1, 1);
ALLOC_CACHE_ALIGN_BUFFER(env_t, tmp_env2, 1);
mmc_initialize(NULL);
mmc = find_mmc_device(dev);
errmsg = init_mmc_for_env(mmc);
if (errmsg) {
ret = -EIO;
goto err;
}
if (mmc_get_env_addr(mmc, 0, &offset1) ||
mmc_get_env_addr(mmc, 1, &offset2)) {
ret = -EIO;
goto fini;
}
if (IS_ENABLED(ENV_MMC_HWPART_REDUND)) {
ret = mmc_set_env_part(mmc, 1);
if (ret)
goto fini;
}
read1_fail = read_env(mmc, CONFIG_ENV_SIZE, offset1, tmp_env1);
if (IS_ENABLED(ENV_MMC_HWPART_REDUND)) {
ret = mmc_set_env_part(mmc, 2);
if (ret)
goto fini;
}
read2_fail = read_env(mmc, CONFIG_ENV_SIZE, offset2, tmp_env2);
ret = env_import_redund((char *)tmp_env1, read1_fail, (char *)tmp_env2,
read2_fail, H_EXTERNAL);
printf("Reading from %sMMC(%d)... ", gd->env_valid == ENV_REDUND ? "redundant " : "", dev);
fini:
fini_mmc_for_env(mmc);
err:
if (ret)
env_set_default(errmsg, 0);
return ret;
}
#else /* ! CONFIG_SYS_REDUNDAND_ENVIRONMENT */
static int env_mmc_load(void)
{
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
struct mmc *mmc;
u32 offset;
int ret;
int dev = mmc_get_env_dev();
const char *errmsg;
env_t *ep = NULL;
mmc = find_mmc_device(dev);
errmsg = init_mmc_for_env(mmc);
if (errmsg) {
ret = -EIO;
goto err;
}
if (mmc_get_env_addr(mmc, 0, &offset)) {
ret = -EIO;
goto fini;
}
if (read_env(mmc, CONFIG_ENV_SIZE, offset, buf)) {
errmsg = "!read failed";
ret = -EIO;
goto fini;
}
printf("Reading from MMC(%d)... ", dev);
ret = env_import(buf, 1, H_EXTERNAL);
if (!ret) {
ep = (env_t *)buf;
gd->env_addr = (ulong)&ep->data;
}
fini:
fini_mmc_for_env(mmc);
err:
if (ret)
env_set_default(errmsg, 0);
return ret;
}
#endif /* CONFIG_SYS_REDUNDAND_ENVIRONMENT */
U_BOOT_ENV_LOCATION(mmc) = {
.location = ENVL_MMC,
ENV_NAME("MMC")
.load = env_mmc_load,
#if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_SPL_BUILD)
.save = env_save_ptr(env_mmc_save),
.erase = ENV_ERASE_PTR(env_mmc_erase)
#endif
};