summaryrefslogtreecommitdiff
path: root/env/mtd.c
diff options
context:
space:
mode:
authorChristian Marangi <ansuelsmth@gmail.com>2025-04-07 20:59:51 +0200
committerTom Rini <trini@konsulko.com>2025-05-05 14:16:59 -0600
commit03fb08d4aef8c342b583e148d1b5c4d289c5572f (patch)
treebdc197033132b4e2cc1254129bab55b5a9084f04 /env/mtd.c
parentf717d798becf2dffd5816484962c350808e98a18 (diff)
env: Introduce support for MTD
Introduce support for env in generic MTD. Currently we only support SPI flash based on the lagacy sf cmd that assume SPI flash are always NOR. This is not the case as to SPI controller also NAND can be attached. To support also these flash scenario, add support for storing and reading ENV from generic MTD device by adding an env driver that base entirely on the MTD api. Introduce a new kconfig CONFIG_ENV_IS_IN_MTD and CONFIG_ENV_MTD_DEV to define the name of the MTD device as exposed by mtd list. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Diffstat (limited to 'env/mtd.c')
-rw-r--r--env/mtd.c338
1 files changed, 338 insertions, 0 deletions
diff --git a/env/mtd.c b/env/mtd.c
new file mode 100644
index 00000000000..721faebd8f2
--- /dev/null
+++ b/env/mtd.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Author: Christian Marangi <ansuelsmth@gmail.com>
+ */
+#include <env_internal.h>
+#include <errno.h>
+#include <malloc.h>
+#include <mtd.h>
+#include <asm/cache.h>
+#include <asm/global_data.h>
+#include <linux/mtd/mtd.h>
+#include <u-boot/crc.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static int setup_mtd_device(struct mtd_info **mtd_env)
+{
+ struct mtd_info *mtd;
+
+ mtd_probe_devices();
+
+ mtd = get_mtd_device_nm(CONFIG_ENV_MTD_DEV);
+ if (IS_ERR_OR_NULL(mtd)) {
+ env_set_default("get_mtd_device_nm() failed", 0);
+ return mtd ? PTR_ERR(mtd) : -EINVAL;
+ }
+
+ *mtd_env = mtd;
+
+ return 0;
+}
+
+static int env_mtd_save(void)
+{
+ char *saved_buf, *write_buf, *tmp;
+ struct erase_info ei = { };
+ struct mtd_info *mtd_env;
+ u32 sect_size, sect_num;
+ size_t ret_len = 0;
+ u32 write_size;
+ env_t env_new;
+ int remaining;
+ u32 offset;
+ int ret;
+
+ ret = setup_mtd_device(&mtd_env);
+ if (ret)
+ return ret;
+
+ sect_size = mtd_env->erasesize;
+
+ /* Is the sector larger than the env (i.e. embedded) */
+ if (sect_size > CONFIG_ENV_SIZE) {
+ saved_buf = malloc(sect_size);
+ if (!saved_buf) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ offset = CONFIG_ENV_OFFSET;
+ remaining = sect_size;
+ tmp = saved_buf;
+
+ while (remaining) {
+ /* Skip the block if it is bad */
+ if (!(offset % sect_size) &&
+ mtd_block_isbad(mtd_env, offset)) {
+ offset += sect_size;
+ continue;
+ }
+
+ ret = mtd_read(mtd_env, offset, mtd_env->writesize,
+ &ret_len, tmp);
+ if (ret)
+ goto done;
+
+ tmp += ret_len;
+ offset += ret_len;
+ remaining -= ret_len;
+ }
+ }
+
+ ret = env_export(&env_new);
+ if (ret)
+ goto done;
+
+ sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size);
+
+ ei.mtd = mtd_env;
+ ei.addr = CONFIG_ENV_OFFSET;
+ ei.len = sect_num * sect_size;
+
+ puts("Erasing MTD...");
+ ret = mtd_erase(mtd_env, &ei);
+ if (ret)
+ goto done;
+
+ if (sect_size > CONFIG_ENV_SIZE) {
+ memcpy(saved_buf, &env_new, CONFIG_ENV_SIZE);
+ write_size = sect_size;
+ write_buf = saved_buf;
+ } else {
+ write_size = sect_num * sect_size;
+ write_buf = (char *)&env_new;
+ }
+
+ offset = CONFIG_ENV_OFFSET;
+ remaining = sect_size;
+ tmp = write_buf;
+
+ puts("Writing to MTD...");
+ while (remaining) {
+ /* Skip the block if it is bad */
+ if (!(offset % sect_size) &&
+ mtd_block_isbad(mtd_env, offset)) {
+ offset += sect_size;
+ continue;
+ }
+
+ ret = mtd_write(mtd_env, offset, mtd_env->writesize,
+ &ret_len, tmp);
+ if (ret)
+ goto done;
+
+ offset += mtd_env->writesize;
+ remaining -= ret_len;
+ tmp += ret_len;
+ }
+
+ ret = 0;
+ puts("done\n");
+
+done:
+ if (saved_buf)
+ free(saved_buf);
+
+ return ret;
+}
+
+static int env_mtd_load(void)
+{
+ struct mtd_info *mtd_env;
+ char *buf, *tmp;
+ size_t ret_len;
+ int remaining;
+ u32 sect_size;
+ u32 offset;
+ int ret;
+
+ buf = (char *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE);
+ if (!buf) {
+ env_set_default("memalign() failed", 0);
+ return -EIO;
+ }
+
+ ret = setup_mtd_device(&mtd_env);
+ if (ret)
+ goto out;
+
+ sect_size = mtd_env->erasesize;
+
+ offset = CONFIG_ENV_OFFSET;
+ remaining = CONFIG_ENV_SIZE;
+ tmp = buf;
+
+ while (remaining) {
+ /* Skip the block if it is bad */
+ if (!(offset % sect_size) &&
+ mtd_block_isbad(mtd_env, offset)) {
+ offset += sect_size;
+ continue;
+ }
+
+ ret = mtd_read(mtd_env, offset, mtd_env->writesize,
+ &ret_len, tmp);
+ if (ret) {
+ env_set_default("mtd_read() failed", 1);
+ goto out;
+ }
+
+ tmp += ret_len;
+ offset += ret_len;
+ remaining -= ret_len;
+ }
+
+ ret = env_import(buf, 1, H_EXTERNAL);
+ if (!ret)
+ gd->env_valid = ENV_VALID;
+
+out:
+ free(buf);
+
+ return ret;
+}
+
+static int env_mtd_erase(void)
+{
+ struct mtd_info *mtd_env;
+ u32 sect_size, sect_num;
+ char *saved_buf, *tmp;
+ struct erase_info ei;
+ size_t ret_len;
+ int remaining;
+ u32 offset;
+ int ret;
+
+ ret = setup_mtd_device(&mtd_env);
+ if (ret)
+ return ret;
+
+ sect_size = mtd_env->erasesize;
+
+ /* Is the sector larger than the env (i.e. embedded) */
+ if (sect_size > CONFIG_ENV_SIZE) {
+ saved_buf = malloc(sect_size);
+ if (!saved_buf) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ offset = CONFIG_ENV_OFFSET;
+ remaining = sect_size;
+ tmp = saved_buf;
+
+ while (remaining) {
+ /* Skip the block if it is bad */
+ if (!(offset % sect_size) &&
+ mtd_block_isbad(mtd_env, offset)) {
+ offset += sect_size;
+ continue;
+ }
+
+ ret = mtd_read(mtd_env, offset, mtd_env->writesize,
+ &ret_len, tmp);
+ if (ret)
+ goto done;
+
+ tmp += ret_len;
+ offset += ret_len;
+ remaining -= ret_len;
+ }
+ }
+
+ sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size);
+
+ ei.mtd = mtd_env;
+ ei.addr = CONFIG_ENV_OFFSET;
+ ei.len = sect_num * sect_size;
+
+ ret = mtd_erase(mtd_env, &ei);
+ if (ret)
+ goto done;
+
+ if (sect_size > CONFIG_ENV_SIZE) {
+ memset(saved_buf, 0, CONFIG_ENV_SIZE);
+
+ offset = CONFIG_ENV_OFFSET;
+ remaining = sect_size;
+ tmp = saved_buf;
+
+ while (remaining) {
+ /* Skip the block if it is bad */
+ if (!(offset % sect_size) &&
+ mtd_block_isbad(mtd_env, offset)) {
+ offset += sect_size;
+ continue;
+ }
+
+ ret = mtd_write(mtd_env, offset, mtd_env->writesize,
+ &ret_len, tmp);
+ if (ret)
+ goto done;
+
+ offset += mtd_env->writesize;
+ remaining -= ret_len;
+ tmp += ret_len;
+ }
+ }
+
+ ret = 0;
+
+done:
+ if (saved_buf)
+ free(saved_buf);
+
+ return ret;
+}
+
+__weak void *env_mtd_get_env_addr(void)
+{
+ return (void *)CONFIG_ENV_ADDR;
+}
+
+/*
+ * Check if Environment on CONFIG_ENV_ADDR is valid.
+ */
+static int env_mtd_init_addr(void)
+{
+ env_t *env_ptr = (env_t *)env_mtd_get_env_addr();
+
+ if (!env_ptr)
+ return -ENOENT;
+
+ if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
+ gd->env_addr = (ulong)&env_ptr->data;
+ gd->env_valid = ENV_VALID;
+ } else {
+ gd->env_valid = ENV_INVALID;
+ }
+
+ return 0;
+}
+
+static int env_mtd_init(void)
+{
+ int ret;
+
+ ret = env_mtd_init_addr();
+ if (ret != -ENOENT)
+ return ret;
+
+ /*
+ * return here -ENOENT, so env_init()
+ * can set the init bit and later if no
+ * other Environment storage is defined
+ * can set the default environment
+ */
+ return -ENOENT;
+}
+
+U_BOOT_ENV_LOCATION(mtd) = {
+ .location = ENVL_MTD,
+ ENV_NAME("MTD")
+ .load = env_mtd_load,
+ .save = ENV_SAVE_PTR(env_mtd_save),
+ .erase = ENV_ERASE_PTR(env_mtd_erase),
+ .init = env_mtd_init,
+};