BootROM of Amlogic SoCs that use ADNL/Optimus protocols needs special layout of "bootloader" partition. So let's implement functions that support bootloader writing on such SoCs. This is prerequisite for ADNL/Optimus implementation.
We place such functions to 'arch/arm/mach-meson', because this code is also needed by 'fastboot' protocol which can also write "bootloader" partition. Signed-off-by: Arseniy Krasnov <avkras...@salutedevices.com> --- arch/arm/include/asm/arch-meson/nand.h | 34 +++ arch/arm/include/asm/arch-meson/spinand.h | 43 ++++ arch/arm/mach-meson/Kconfig | 31 +++ arch/arm/mach-meson/Makefile | 4 +- arch/arm/mach-meson/rawnand.c | 291 ++++++++++++++++++++++ arch/arm/mach-meson/spinand.c | 158 ++++++++++++ 6 files changed, 559 insertions(+), 2 deletions(-) create mode 100644 arch/arm/include/asm/arch-meson/nand.h create mode 100644 arch/arm/include/asm/arch-meson/spinand.h create mode 100644 arch/arm/mach-meson/rawnand.c create mode 100644 arch/arm/mach-meson/spinand.c diff --git a/arch/arm/include/asm/arch-meson/nand.h b/arch/arm/include/asm/arch-meson/nand.h new file mode 100644 index 00000000000..1f5a20d237b --- /dev/null +++ b/arch/arm/include/asm/arch-meson/nand.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2023-2024 SaluteDevices, Inc. + */ + +#ifndef __MESON_NAND_H__ +#define __MESON_NAND_H__ + +#define BOOT_LOADER "bootloader" +#define BOOT_BL2 "bl2" +#define BOOT_SPL "spl" +#define BOOT_TPL "tpl" +#define BOOT_FIP "fip" + +#define BL2_COPY_NUM (CONFIG_MESON_BL2_COPY_NUM) +#define BL2_IMAGE_SIZE_PER_COPY (CONFIG_MESON_BL2_IMAGE_SIZE) +#define BL2_TOTAL_PAGES 1024 + +#define TPL_COPY_NUM (CONFIG_MESON_TPL_COPY_NUM) +#define TPL_SIZE_PER_COPY 0x200000 + +#define NAND_RSV_BLOCK_NUM 48 + +/** + * meson_bootloader_write - write 'bootloader' partition to NAND + * according to the required layout. It will + * write BL2, TPL and info pages. + * + * @buf: buffer with BL2 and TPL + * @length: buffer length + * @return: 0 on success, -errno otherwise. + */ +int meson_bootloader_write(unsigned char *buf, size_t length); +#endif diff --git a/arch/arm/include/asm/arch-meson/spinand.h b/arch/arm/include/asm/arch-meson/spinand.h new file mode 100644 index 00000000000..1628c8bd4e0 --- /dev/null +++ b/arch/arm/include/asm/arch-meson/spinand.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2024 SaluteDevices, Inc. + */ + +#ifndef __MESON_SPINAND_H__ +#define __MESON_SPINAND_H__ + +#include <stdint.h> +#include <string.h> +#include <asm/types.h> +#include <asm/arch-meson/nand.h> +#include <linux/compiler_attributes.h> +#include <linux/mtd/mtd.h> + +struct meson_spi_nand_info_page { + char magic[8]; /* Magic header of info page. */ + /* Info page version, +1 when you update this struct. */ + u8 version; /* 1 for now. */ + u8 mode; /* 1 discrete, 0 compact. */ + u8 bl2_num; /* bl2 copy number. */ + u8 fip_num; /* fip copy number. */ + union { + struct { + u8 rd_max; /* spi nand max read io. */ + u8 oob_offset; /* User bytes offset. */ + u8 planes_per_lun; /* number of planes per LUN. */ + u8 rsv; /* Reserved gap. */ + u32 fip_start; /* Start pages. */ + u32 fip_pages; /* Pages per fip. */ + u32 page_size; /* spi nand page size (bytes). */ + u32 page_per_blk; /* Page number per block. */ + u32 oob_size; /* Valid oob size (bytes). */ + u32 bbt_start; /* BBT start pages. */ + u32 bbt_valid; /* BBT valid offset pages. */ + u32 bbt_size; /* BBT occupied bytes. */ + } __packed spinand; /* spi nand. */ + struct { + u32 reserved; + } emmc; + } dev; +} __packed; +#endif diff --git a/arch/arm/mach-meson/Kconfig b/arch/arm/mach-meson/Kconfig index 7570f48e25f..883ee78f49b 100644 --- a/arch/arm/mach-meson/Kconfig +++ b/arch/arm/mach-meson/Kconfig @@ -93,4 +93,35 @@ config SYS_BOARD Based on this option board/<CONFIG_SYS_VENDOR>/<CONFIG_SYS_BOARD> will be used. +config MESON_BL2_COPY_NUM + depends on ADNL || FASTBOOT_FLASH + int "Number of BL2 copies written to storage" + default 8 + help + The ADNL / Optimus / fastboot protocol writes several copies of BL2 + bootloader during firmware update process. + +config MESON_TPL_COPY_NUM + depends on ADNL || FASTBOOT_FLASH + int "Number of TPL copies written to storage" + default 4 + help + The ADNL / Optimus / fastboot protocol writes several copies of TPL + bootloader during firmware update process. + +config MESON_BL2_IMAGE_SIZE + depends on ADNL || FASTBOOT_FLASH + hex "Size of BL2 image" + default 0x10000 + help + Size of BL2 image, passed by ADNL / Optimus / fastboot protocols. + +config MESON_BL2_IMAGE_OFFSET + depends on ADNL || FASTBOOT_FLASH + hex "Offset from info page to BL2 image" + default 0x800 + help + For each copy of BL2, this is offset from info page to BL2 image. Used + by Optimus and fastboot protocols. + endif diff --git a/arch/arm/mach-meson/Makefile b/arch/arm/mach-meson/Makefile index 535b0878b91..f63f91dde35 100644 --- a/arch/arm/mach-meson/Makefile +++ b/arch/arm/mach-meson/Makefile @@ -4,6 +4,6 @@ obj-y += board-common.o sm.o board-info.o obj-$(CONFIG_MESON_GX) += board-gx.o -obj-$(CONFIG_MESON_AXG) += board-axg.o +obj-$(CONFIG_MESON_AXG) += board-axg.o rawnand.o obj-$(CONFIG_MESON_G12A) += board-g12a.o -obj-$(CONFIG_MESON_A1) += board-a1.o +obj-$(CONFIG_MESON_A1) += board-a1.o spinand.o diff --git a/arch/arm/mach-meson/rawnand.c b/arch/arm/mach-meson/rawnand.c new file mode 100644 index 00000000000..6b91674200e --- /dev/null +++ b/arch/arm/mach-meson/rawnand.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2024 SaluteDevices, Inc. + */ + +#include <nand.h> +#include <asm/types.h> +#include <asm/arch/rawnand.h> +#include <linux/compiler_attributes.h> +#include <linux/mtd/mtd.h> +#include <linux/sizes.h> + +struct raw_nand_setup { + union { + u32 d32; + struct { + unsigned cmd:22; /* NAND controller cmd to read. */ + unsigned large_page:1; /* Page is large. */ + unsigned no_rb:1; /* No RB. */ + unsigned a2:1; /* A2 cmd enable flag. */ + unsigned reserved25:1; /* Gap. */ + unsigned page_list:1; /* Page list is enabled. */ + unsigned sync_mode:2; /* Always 0 according vendor doc. */ + unsigned size:2; /* Always 0 according vendor doc. */ + unsigned active:1; /* Always 0 according vendor doc. */ + } b; + } cfg; + u16 id; + u16 max; +} __packed; + +struct raw_nand_cmd { + unsigned char type; + unsigned char val; +} __packed; + +struct raw_ext_info { + u32 read_info; + u32 new_type; + u32 page_per_blk; + u32 xlc; + u32 ce_mask; + u32 boot_num; + u32 each_boot_pages; + /* for compatible reason */ + u32 bbt_occupy_pages; + u32 bbt_start_block; +} __packed; + +struct raw_fip_info { + u16 version; + u16 mode; + u32 fip_start; +} __packed; + +struct raw_nand_info_page { + struct raw_nand_setup nand_setup; + unsigned char page_list[16]; + struct raw_nand_cmd retry_usr[32]; + struct raw_ext_info ext_info; + struct raw_fip_info fip_info; + u32 ddrp_start_page; +} __packed; + +static void meson_raw_fill_oobbuf_with_magic(u8 *oobbuf, size_t oobavail) +{ + for (size_t j = 0; j < oobavail; j += 2) { + oobbuf[j] = 0x55; + oobbuf[j + 1] = 0xaa; + } +} + +static int meson_raw_raw_nand_info_page_prepare(struct mtd_info *mtd, void *oobbuf) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd->parent); + struct raw_nand_info_page *p_nand_page0; + struct raw_ext_info *ext_info; + struct raw_nand_setup *nand_setup; + struct raw_fip_info *fip_info; + u32 config_data; + + p_nand_page0 = oobbuf; + memset(p_nand_page0, 0x0, sizeof(struct raw_nand_info_page)); + nand_setup = &p_nand_page0->nand_setup; + ext_info = &p_nand_page0->ext_info; + + config_data = CMDRWGEN(NFC_CMD_N2M, 0, 1, 0, nand_chip->ecc.size >> 3, + nand_chip->ecc.steps); + + config_data = config_data | BIT(23) | BIT(22) | BIT(21); + nand_setup->cfg.d32 = config_data; + nand_setup->id = 0; + nand_setup->max = 0; + + ext_info->read_info = 1; + ext_info->page_per_blk = mtd->erasesize / mtd->writesize; + ext_info->ce_mask = 0x01; + ext_info->xlc = 1; + ext_info->boot_num = TPL_COPY_NUM; + ext_info->each_boot_pages = TPL_SIZE_PER_COPY / mtd->writesize; + ext_info->bbt_occupy_pages = 0x1; + ext_info->bbt_start_block = 0x14; /* Amlogic keeps BBT in hardcoded offset. */ + + p_nand_page0->ddrp_start_page = 0xb00; + + fip_info = &p_nand_page0->fip_info; + fip_info->version = 1; + fip_info->mode = 1; /* Discrete mode. */ + fip_info->fip_start = BL2_TOTAL_PAGES + + (NAND_RSV_BLOCK_NUM * ext_info->page_per_blk); + + return 0; +} + +static int meson_raw_write_info_page(struct mtd_info *mtd, loff_t offset, size_t *actual) +{ + int ret; + struct mtd_oob_ops ops; + u8 *datbuf = (u8 *)calloc(mtd->writesize, sizeof(u8)); + + if (!datbuf) { + pr_err("Failed to allocate data buf for info page\n"); + return -ENOMEM; + } + + meson_raw_raw_nand_info_page_prepare(mtd, datbuf); + + memset(&ops, 0, sizeof(struct mtd_oob_ops)); + ops.len = mtd->writesize; + ops.datbuf = datbuf; + ops.ooblen = mtd->oobavail; + ops.mode = MTD_OPS_AUTO_OOB; + ops.oobbuf = calloc(mtd->oobavail, sizeof(u8)); + + if (!ops.oobbuf) { + pr_err("Failed to allocate OOB buf for info page\n"); + free(datbuf); + return -ENOMEM; + } + + meson_raw_fill_oobbuf_with_magic(ops.oobbuf, mtd->oobavail); + + ret = mtd_write_oob(mtd, offset, &ops); + *actual = ops.retlen; + + free(ops.oobbuf); + free(datbuf); + + return ret; +} + +static int meson_raw_write_bl2(unsigned char *src_data) +{ + int ret; + size_t i; + nand_erase_options_t opts; + struct mtd_info *mtd; + size_t bl2_copy_limit; + struct nand_chip *nand; + size_t oobbuff_size; + struct mtd_oob_ops ops; + size_t src_size = BL2_IMAGE_SIZE_PER_COPY; + loff_t dest = 0; + size_t copy_num = BL2_COPY_NUM; + + pr_info("Writing BL2\n"); + + mtd = get_mtd_device_nm(BOOT_LOADER); + if (IS_ERR_OR_NULL(mtd)) { + pr_err("Failed to get mtd device \"bootloader\" ret: %ld\n", PTR_ERR(mtd)); + return PTR_ERR(mtd); + } + + if (mtd->oobavail & 1) { + pr_err("Invalid OOB available bytes: %u\n", mtd->oobavail); + return -ENODEV; + } + + memset(&opts, 0, sizeof(opts)); + opts.offset = dest; + opts.length = mtd->size; + if (nand_erase_opts(mtd, &opts)) { + pr_err("Failed to erase \"bootloader\"\n"); + return -EIO; + } + + bl2_copy_limit = mtd->size / BL2_COPY_NUM; + + memset(&ops, 0, sizeof(struct mtd_oob_ops)); + ops.len = src_size; + ops.datbuf = src_data; + ops.ooblen = mtd->oobavail * (src_size / mtd->writesize + 1); + ops.mode = MTD_OPS_AUTO_OOB; + + nand = mtd_to_nand(mtd->parent); + oobbuff_size = (mtd->writesize / nand->ecc.size) * ops.ooblen; + + ops.oobbuf = calloc(oobbuff_size, sizeof(u8)); + + if (!ops.oobbuf) { + pr_err("Failed to allocate OOB buf for BL2\n"); + return -ENOMEM; + } + + meson_raw_fill_oobbuf_with_magic(ops.oobbuf, ops.ooblen); + + for (i = 0; i < copy_num; i++) { + size_t actual = 0; + loff_t offset_in_partition = dest + i * bl2_copy_limit; + + if (mtd_block_isbad(mtd, offset_in_partition)) { + pr_info("Skip badblock at %llx\n", offset_in_partition); + continue; + } + + ret = meson_raw_write_info_page(mtd, offset_in_partition, &actual); + if (ret) + goto free_oobbuf; + + offset_in_partition += CONFIG_MESON_BL2_IMAGE_OFFSET + + (actual - mtd->writesize); + ret = mtd_write_oob(mtd, offset_in_partition, &ops); + if (ret) + goto free_oobbuf; + } + +free_oobbuf: + free(ops.oobbuf); + + return ret; +} + +static int meson_raw_write_tpl(unsigned char *src_data, size_t src_size) +{ + int ret; + size_t i; + struct mtd_info *mtd; + nand_erase_options_t opts; + loff_t dest = 0; + size_t copy_num = TPL_COPY_NUM; + + pr_info("Writing TPL\n"); + mtd = get_mtd_device_nm(BOOT_TPL); + + if (IS_ERR_OR_NULL(mtd)) { + pr_err("Failed to get mtd device \"tpl\" ret: %ld\n", PTR_ERR(mtd)); + return PTR_ERR(mtd); + } + + memset(&opts, 0, sizeof(nand_erase_options_t)); + opts.offset = dest; + opts.length = mtd->size; + if (nand_erase_opts(mtd, &opts)) { + pr_err("Failed to erase \"tpl\"\n"); + return -EIO; + } + + for (i = 0; i < copy_num; i++) { + loff_t offset_in_partition = dest + i * TPL_SIZE_PER_COPY; + + ret = nand_write_skip_bad(mtd, offset_in_partition, &src_size, + NULL, mtd->size / TPL_COPY_NUM, src_data, 0); + if (ret) + return ret; + } + + return 0; +} + +int meson_bootloader_write(unsigned char *buf, size_t length) +{ + int ret; + + if (!buf) { + pr_err("Empty buffer is received\n"); + return -EINVAL; + } + + ret = meson_raw_write_bl2(buf); + if (ret) + return ret; + + /* Write FIP. FIP is also called TPL */ + ret = meson_raw_write_tpl(buf + BL2_IMAGE_SIZE_PER_COPY, + length - BL2_IMAGE_SIZE_PER_COPY); + if (ret) + return ret; + + pr_info("\"Bootloader (BL2+TPL)\" is written\n"); + return 0; +} diff --git a/arch/arm/mach-meson/spinand.c b/arch/arm/mach-meson/spinand.c new file mode 100644 index 00000000000..dbad4ba0e9f --- /dev/null +++ b/arch/arm/mach-meson/spinand.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2023 SaluteDevices, Inc. + */ + +#include <nand.h> +#include <asm/arch/spinand.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/sizes.h> + +#define SPINAND_MAGIC "AMLIFPG" +#define SPINAND_INFO_VER 1 + +static void meson_bootloader_setup_info_page(struct meson_spi_nand_info_page *info_page, + struct mtd_info *mtd) +{ + struct nand_device *nand_dev; + u32 page_per_blk; + + nand_dev = mtd_to_nanddev(mtd->parent); + page_per_blk = mtd->erasesize / mtd->writesize; + info_page->version = SPINAND_INFO_VER; + /* DISCRETE only */ + info_page->mode = 1; + info_page->bl2_num = BL2_COPY_NUM; + info_page->fip_num = TPL_COPY_NUM; + info_page->dev.spinand.rd_max = 2; + info_page->dev.spinand.fip_start = BL2_TOTAL_PAGES + NAND_RSV_BLOCK_NUM * page_per_blk; + info_page->dev.spinand.fip_pages = TPL_SIZE_PER_COPY / mtd->writesize; + info_page->dev.spinand.page_size = mtd->writesize; + info_page->dev.spinand.page_per_blk = page_per_blk; + info_page->dev.spinand.oob_size = mtd->oobsize; + info_page->dev.spinand.oob_offset = 0; + info_page->dev.spinand.bbt_start = 0; + info_page->dev.spinand.bbt_valid = 0; + info_page->dev.spinand.bbt_size = 0; + info_page->dev.spinand.planes_per_lun = nand_dev->memorg.planes_per_lun; +} + +static int meson_store_boot_write_bl2(struct mtd_info *mtd, void *buf, unsigned int copy_idx, + struct meson_spi_nand_info_page *info_page) +{ + unsigned int size_per_copy; + loff_t offset; + size_t retlen; + int ret; + + size_per_copy = mtd->writesize * (BL2_TOTAL_PAGES / BL2_COPY_NUM); + offset = mtd->offset + (copy_idx * size_per_copy); + ret = mtd_write(mtd, offset, BL2_IMAGE_SIZE_PER_COPY, &retlen, buf); + if (ret) + return ret; + + /* + * Info page is written directly after each BL2 image. For + * example let: + * BL2_TOTAL_PAGES = 1024 + * BL2_COPY_NUM = 8 + * BL2_IMAGE_SIZE_PER_COPY = 65536 = 0x10000 + * size_per_copy = 2048 * (1024 / 8) = 0x40000 + * + * BL2 image will be written to BL2_COPY_NUM + * locations: + * 0x00000 + * 0x40000 + * 0x80000 + * 0xc0000 + * 0x100000 + * 0x140000 + * 0x180000 + * 0x1c0000 + * + * Info pages will be written to BL2_COPY_NUM + * locations: + * 0x10000 + * 0x50000 + * 0x90000 + * 0xd0000 + * 0x110000 + * 0x150000 + * 0x190000 + * 0x1d0000 + * + * Here is the same, in picture: + * + * BL2_TOTAL_PAGES == 1024 + * BL2_COPY_NUM == 8 + * /---------------------------------------\ + * | | + * + * |AB |AB |AB |AB |AB |AB |AB |AB | + * |____|____|____|____|____|____|____|____| + * ^ ^ + * | | + * |____|__ size_per_copy == 0x40000 + * + * A - is BL2 image. It is BL2_IMAGE_SIZE_PER_COPY == 0x10000 bytes. + * B - is info page. + */ + offset += BL2_IMAGE_SIZE_PER_COPY; + + return mtd_write(mtd, offset, sizeof(*info_page), + &retlen, (u8 *)info_page); +} + +static int meson_store_boot_write_tpl(void *buf, size_t size, unsigned int copy_idx) +{ + loff_t offset; + struct mtd_info *mtd; + loff_t limit; + size_t retlen = 0; + + mtd = get_mtd_device_nm(BOOT_TPL); + if (IS_ERR_OR_NULL(mtd)) + return -ENODEV; + + offset = mtd->offset + (copy_idx * TPL_SIZE_PER_COPY); + limit = offset + TPL_SIZE_PER_COPY; + + return nand_write_skip_bad(mtd->parent, offset, &size, &retlen, + limit, buf, 0); +} + +int meson_bootloader_write(unsigned char *buf, size_t length) +{ + struct meson_spi_nand_info_page info_page; + unsigned int copy_idx; + struct mtd_info *mtd; + int ret; + + /* 'buf' contains BL2 and TPL. If there is only BL2 or + * even less - input data is invalid. + */ + if (length <= BL2_IMAGE_SIZE_PER_COPY) + return -EINVAL; + + mtd = get_mtd_device_nm(BOOT_LOADER); + if (IS_ERR_OR_NULL(mtd)) + return -ENODEV; + + meson_bootloader_setup_info_page(&info_page, mtd); + + for (copy_idx = 0; copy_idx < BL2_COPY_NUM; copy_idx++) { + ret = meson_store_boot_write_bl2(mtd, buf, copy_idx, &info_page); + if (ret) + return ret; + } + + for (copy_idx = 0; copy_idx < TPL_COPY_NUM; copy_idx++) { + ret = meson_store_boot_write_tpl(buf + BL2_IMAGE_SIZE_PER_COPY, + length - BL2_IMAGE_SIZE_PER_COPY, copy_idx); + if (ret) + return ret; + } + + return 0; +} -- 2.30.1