diff options
author | Álvaro Fernández Rojas <noltari@gmail.com> | 2020-05-14 18:19:35 +0200 |
---|---|---|
committer | Álvaro Fernández Rojas <noltari@gmail.com> | 2020-05-18 18:24:06 +0200 |
commit | cca13d8e8236cb069167d831913b2e6cbfc9ec7c (patch) | |
tree | 0f1d39c4c41a680f0a428d969ee72b6268b009ab /target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_bcm_wfi.c | |
parent | 72985233a6969e85fc07fab35244cfd3ca04409b (diff) | |
download | upstream-cca13d8e8236cb069167d831913b2e6cbfc9ec7c.tar.gz upstream-cca13d8e8236cb069167d831913b2e6cbfc9ec7c.tar.bz2 upstream-cca13d8e8236cb069167d831913b2e6cbfc9ec7c.zip |
kernel: mtdsplit: add BCM WFI support
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Diffstat (limited to 'target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_bcm_wfi.c')
-rw-r--r-- | target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_bcm_wfi.c | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_bcm_wfi.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_bcm_wfi.c new file mode 100644 index 0000000000..9fc3c8ac50 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_bcm_wfi.c @@ -0,0 +1,203 @@ +/* + * MTD split for Broadcom Whole Flash Image + * + * Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define je16_to_cpu(x) ((x).v16) +#define je32_to_cpu(x) ((x).v32) + +#include <linux/crc32.h> +#include <linux/init.h> +#include <linux/jffs2.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/byteorder/generic.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include "mtdsplit.h" + +#define BCM_WFI_PARTS 4 + +#define CFERAM_NAME "cferam" +#define CFERAM_NAME_LEN (sizeof(CFERAM_NAME) - 1) +#define KERNEL_NAME "vmlinux.lz" +#define KERNEL_NAME_LEN (sizeof(KERNEL_NAME) - 1) +#define OPENWRT_NAME "1-openwrt" +#define OPENWRT_NAME_LEN (sizeof(OPENWRT_NAME) - 1) + +#define UBI_MAGIC 0x55424923 + +static u32 jffs2_dirent_crc(struct jffs2_raw_dirent *node) +{ + return crc32(0, node, sizeof(struct jffs2_raw_dirent) - 8); +} + +static bool jffs2_dirent_valid(struct jffs2_raw_dirent *node) +{ + return ((je16_to_cpu(node->magic) == JFFS2_MAGIC_BITMASK) && + (je16_to_cpu(node->nodetype) == JFFS2_NODETYPE_DIRENT) && + je32_to_cpu(node->ino) && + je32_to_cpu(node->node_crc) == jffs2_dirent_crc(node)); +} + +static int jffs2_find_file(struct mtd_info *master, uint8_t *buf, + const char *name, size_t name_len, + loff_t *offs) +{ + struct jffs2_raw_dirent *node; + bool valid = false; + size_t retlen; + uint16_t magic; + int rc; + + for (; *offs < master->size; *offs += master->erasesize) { + unsigned int block_offs = 0; + + /* Skip CFE erased blocks */ + rc = mtd_read(master, *offs, sizeof(magic), &retlen, + (void *) &magic); + if (rc || retlen != sizeof(magic)) { + continue; + } + + /* Skip blocks not starting with JFFS2 magic */ + if (magic != JFFS2_MAGIC_BITMASK) + continue; + + /* Read full block */ + rc = mtd_read(master, *offs, master->erasesize, &retlen, + (void *) buf); + if (rc) + return rc; + if (retlen != master->erasesize) + return -EINVAL; + + while (block_offs < master->erasesize) { + node = (struct jffs2_raw_dirent *) &buf[block_offs]; + + if (!jffs2_dirent_valid(node)) { + block_offs += 4; + continue; + } + + if (!memcmp(node->name, OPENWRT_NAME, + OPENWRT_NAME_LEN)) + valid = true; + else if (!memcmp(node->name, name, name_len)) + return valid ? 0 : -EINVAL; + + block_offs += je32_to_cpu(node->totlen); + block_offs = (block_offs + 0x3) & ~0x3; + } + } + + return -ENOENT; +} + +static int ubifs_find(struct mtd_info *master, loff_t *offs) +{ + uint32_t magic; + size_t retlen; + int rc; + + for (; *offs < master->size; *offs += master->erasesize) { + rc = mtd_read(master, *offs, sizeof(magic), &retlen, + (unsigned char *) &magic); + if (rc || retlen != sizeof(magic)) + continue; + + if (be32_to_cpu(magic) == UBI_MAGIC) + return 0; + } + + return -ENOENT; +} + +static int mtdsplit_parse_bcm_wfi(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + loff_t cfe_off, kernel_off, rootfs_off; + uint8_t *buf; + int ret; + + buf = kzalloc(master->erasesize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + cfe_off = 0; + ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN, + &cfe_off); + if (ret) { + kfree(buf); + return ret; + } + + kernel_off = cfe_off + master->erasesize; + ret = jffs2_find_file(master, buf, KERNEL_NAME, KERNEL_NAME_LEN, + &kernel_off); + kfree(buf); + if (ret) + return ret; + + rootfs_off = kernel_off + master->erasesize; + ret = ubifs_find(master, &rootfs_off); + if (ret) + return ret; + + parts = kzalloc(BCM_WFI_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = "cferam"; + parts[0].mask_flags = MTD_WRITEABLE; + parts[0].offset = 0; + parts[0].size = kernel_off; + + parts[1].name = "firmware"; + parts[1].offset = kernel_off; + parts[1].size = master->size - kernel_off; + + parts[2].name = KERNEL_PART_NAME; + parts[2].offset = kernel_off; + parts[2].size = rootfs_off - kernel_off; + + parts[3].name = UBI_PART_NAME; + parts[3].offset = rootfs_off; + parts[3].size = master->size - rootfs_off; + + *pparts = parts; + + return BCM_WFI_PARTS; +} + +static const struct of_device_id mtdsplit_fit_of_match_table[] = { + { .compatible = "brcm,wfi" }, + { }, +}; + +static struct mtd_part_parser mtdsplit_bcm_wfi_parser = { + .owner = THIS_MODULE, + .name = "bcm-wfi-fw", + .of_match_table = mtdsplit_fit_of_match_table, + .parse_fn = mtdsplit_parse_bcm_wfi, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_bcm_wfi_init(void) +{ + register_mtd_parser(&mtdsplit_bcm_wfi_parser); + + return 0; +} + +module_init(mtdsplit_bcm_wfi_init); |