diff options
Diffstat (limited to 'target/linux/generic/files/drivers/mtd')
12 files changed, 1500 insertions, 0 deletions
diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig new file mode 100644 index 0000000..c4e3418 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig @@ -0,0 +1,51 @@ +config MTD_SPLIT + def_bool n + help + Generic MTD split support. + +config MTD_SPLIT_SUPPORT + def_bool MTD = y + +comment "Rootfs partition parsers" + +config MTD_SPLIT_SQUASHFS_ROOT + bool "Squashfs based root partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + default n + help + This provides a parsing function which allows to detect the + offset and size of the unused portion of a rootfs partition + containing a squashfs. + +comment "Firmware partition parsers" + +config MTD_SPLIT_SEAMA_FW + bool "Seama firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_UIMAGE_FW + bool "uImage based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_FIT_FW + bool "FIT based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_LZMA_FW + bool "LZMA compressed kernel based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TPLINK_FW + bool "TP-Link firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TRX_FW + bool "TRX image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile new file mode 100644 index 0000000..e09476b --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o +obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o +obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o +obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o +obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o +obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o +obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o +obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c new file mode 100644 index 0000000..162739f --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau <nbd@openwrt.org> + * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org> + * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de> + * + * 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 pr_fmt(fmt) "mtdsplit: " fmt + +#include <linux/export.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/magic.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> + +#include "mtdsplit.h" + +struct squashfs_super_block { + __le32 s_magic; + __le32 pad0[9]; + __le64 bytes_used; +}; + +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + struct squashfs_super_block sb; + size_t retlen; + int err; + + err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb); + if (err || (retlen != sizeof(sb))) { + pr_alert("error occured while reading from \"%s\"\n", + master->name); + return -EIO; + } + + if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) { + pr_alert("no squashfs found in \"%s\"\n", master->name); + return -EINVAL; + } + + retlen = le64_to_cpu(sb.bytes_used); + if (retlen <= 0) { + pr_alert("squashfs is empty in \"%s\"\n", master->name); + return -ENODEV; + } + + if (offset + retlen > master->size) { + pr_alert("squashfs has invalid size in \"%s\"\n", + master->name); + return -EINVAL; + } + + *squashfs_len = retlen; + return 0; +} +EXPORT_SYMBOL_GPL(mtd_get_squashfs_len); + +static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset) +{ + return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize; +} + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset) +{ + u32 magic; + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, sizeof(magic), &retlen, + (unsigned char *) &magic); + if (ret) + return ret; + + if (retlen != sizeof(magic)) + return -EIO; + + if (le32_to_cpu(magic) != SQUASHFS_MAGIC && + magic != 0x19852003) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset) +{ + size_t offset; + int err; + + for (offset = from; offset < limit; + offset = mtd_next_eb(mtd, offset)) { + err = mtd_check_rootfs_magic(mtd, offset); + if (err) + continue; + + *ret_offset = offset; + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(mtd_find_rootfs_from); + diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h new file mode 100644 index 0000000..7ee88b0 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau <nbd@openwrt.org> + * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org> + * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de> + * + * 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. + * + */ + +#ifndef _MTDSPLIT_H +#define _MTDSPLIT_H + +#define KERNEL_PART_NAME "kernel" +#define ROOTFS_PART_NAME "rootfs" + +#define ROOTFS_SPLIT_NAME "rootfs_data" + +#ifdef CONFIG_MTD_SPLIT +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len); + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset); + +#else +static inline int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + return -ENODEV; +} + +static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset) +{ + return -EINVAL; +} + +static inline int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset) +{ + return -ENODEV; +} +#endif /* CONFIG_MTD_SPLIT */ + +#endif /* _MTDSPLIT_H */ diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c new file mode 100644 index 0000000..d1087f6 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015 The Linux Foundation + * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/types.h> +#include <linux/byteorder/generic.h> +#include <linux/slab.h> +#include <linux/of_fdt.h> + +#include "mtdsplit.h" + +struct fdt_header { + uint32_t magic; /* magic word FDT_MAGIC */ + uint32_t totalsize; /* total size of DT block */ + uint32_t off_dt_struct; /* offset to structure */ + uint32_t off_dt_strings; /* offset to strings */ + uint32_t off_mem_rsvmap; /* offset to memory reserve map */ + uint32_t version; /* format version */ + uint32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + uint32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + uint32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + uint32_t size_dt_struct; /* size of the structure block */ +}; + +static int +mtdsplit_fit_parse(struct mtd_info *mtd, struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct fdt_header hdr; + size_t hdr_len, retlen; + size_t offset; + size_t fit_offset, fit_size; + size_t rootfs_offset, rootfs_size; + struct mtd_partition *parts; + int ret; + + hdr_len = sizeof(struct fdt_header); + + /* Parse the MTD device & search for the FIT image location */ + for(offset = 0; offset < mtd->size; offset += mtd->erasesize) { + ret = mtd_read(mtd, 0, hdr_len, &retlen, (void*) &hdr); + if (ret) { + pr_err("read error in \"%s\" at offset 0x%llx\n", + mtd->name, (unsigned long long) offset); + return ret; + } + + if (retlen != hdr_len) { + pr_err("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + /* Check the magic - see if this is a FIT image */ + if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) { + pr_debug("no valid FIT image found in \"%s\" at offset %llx\n", + mtd->name, (unsigned long long) offset); + continue; + } + + /* We found a FIT image. Let's keep going */ + break; + } + + fit_offset = offset; + fit_size = be32_to_cpu(hdr.totalsize); + + if (fit_size == 0) { + pr_err("FIT image in \"%s\" at offset %llx has null size\n", + mtd->name, (unsigned long long) fit_offset); + return -ENODEV; + } + + /* Search for the rootfs partition after the FIT image */ + ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, + mtd->size, &rootfs_offset); + if (ret) { + pr_info("no rootfs found after FIT image in \"%s\"\n", + mtd->name); + return ret; + } + + rootfs_size = mtd->size - rootfs_offset; + + parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = fit_offset; + parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return 2; +} + +static struct mtd_part_parser uimage_parser = { + .owner = THIS_MODULE, + .name = "fit-fw", + .parse_fn = mtdsplit_fit_parse, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_fit_init(void) +{ + register_mtd_parser(&uimage_parser); + + return 0; +} + +module_init(mtdsplit_fit_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c new file mode 100644 index 0000000..64dc7cb --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include <asm/unaligned.h> + +#include "mtdsplit.h" + +#define LZMA_NR_PARTS 2 +#define LZMA_PROPERTIES_SIZE 5 + +struct lzma_header { + u8 props[LZMA_PROPERTIES_SIZE]; + u8 size_low[4]; + u8 size_high[4]; +}; + +static int mtdsplit_parse_lzma(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct lzma_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + u32 t; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* verify LZMA properties */ + if (hdr.props[0] >= (9 * 5 * 5)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.props[1]); + if (!is_power_of_2(t)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.size_high); + if (t) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, + master->size, &rootfs_offset); + if (err) + return err; + + parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return LZMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_lzma_parser = { + .owner = THIS_MODULE, + .name = "lzma-fw", + .parse_fn = mtdsplit_parse_lzma, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_lzma_init(void) +{ + register_mtd_parser(&mtdsplit_lzma_parser); + + return 0; +} + +subsys_initcall(mtdsplit_lzma_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c new file mode 100644 index 0000000..6f21f8f --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> + +#include "mtdsplit.h" + +#define SEAMA_MAGIC 0x5EA3A417 +#define SEAMA_NR_PARTS 2 +#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +struct seama_header { + __be32 magic; /* should always be SEAMA_MAGIC. */ + __be16 reserved; /* reserved for */ + __be16 metasize; /* size of the META data */ + __be32 size; /* size of the image */ +}; + +static int mtdsplit_parse_seama(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct seama_header hdr; + size_t hdr_len, retlen, kernel_size; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) + return -EINVAL; + + kernel_size = hdr_len + be32_to_cpu(hdr.size) + + be16_to_cpu(hdr.metasize); + if (kernel_size > master->size) + return -EINVAL; + + /* Find the rootfs after the kernel. */ + err = mtd_check_rootfs_magic(master, kernel_size); + if (!err) { + rootfs_offset = kernel_size; + } else { + /* + * The size in the header might cover the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset); + if (err) + return err; + } + + parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return SEAMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_seama_parser = { + .owner = THIS_MODULE, + .name = "seama-fw", + .parse_fn = mtdsplit_parse_seama, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_seama_init(void) +{ + register_mtd_parser(&mtdsplit_seama_parser); + + return 0; +} + +subsys_initcall(mtdsplit_seama_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c new file mode 100644 index 0000000..3d80e07 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org> + * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org> + * + * 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/magic.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> + +#include "mtdsplit.h" + +static int +mtdsplit_parse_squashfs(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *part; + struct mtd_info *parent_mtd; + size_t part_offset; + size_t squashfs_len; + int err; + + err = mtd_get_squashfs_len(master, 0, &squashfs_len); + if (err) + return err; + + parent_mtd = mtdpart_get_master(master); + part_offset = mtdpart_get_offset(master); + + part = kzalloc(sizeof(*part), GFP_KERNEL); + if (!part) { + pr_alert("unable to allocate memory for \"%s\" partition\n", + ROOTFS_SPLIT_NAME); + return -ENOMEM; + } + + part->name = ROOTFS_SPLIT_NAME; + part->offset = mtd_roundup_to_eb(part_offset + squashfs_len, + parent_mtd) - part_offset; + part->size = master->size - part->offset; + + *pparts = part; + return 1; +} + +static struct mtd_part_parser mtdsplit_squashfs_parser = { + .owner = THIS_MODULE, + .name = "squashfs-split", + .parse_fn = mtdsplit_parse_squashfs, + .type = MTD_PARSER_TYPE_ROOTFS, +}; + +static int __init mtdsplit_squashfs_init(void) +{ + register_mtd_parser(&mtdsplit_squashfs_parser); + + return 0; +} + +subsys_initcall(mtdsplit_squashfs_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c new file mode 100644 index 0000000..00c2d39 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> + +#include "mtdsplit.h" + +#define TPLINK_NR_PARTS 2 +#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +#define MD5SUM_LEN 16 + +struct fw_v1 { + char vendor_name[24]; + char fw_version[36]; + uint32_t hw_id; /* hardware id */ + uint32_t hw_rev; /* hardware revision */ + uint32_t unk1; + uint8_t md5sum1[MD5SUM_LEN]; + uint32_t unk2; + uint8_t md5sum2[MD5SUM_LEN]; + uint32_t unk3; + uint32_t kernel_la; /* kernel load address */ + uint32_t kernel_ep; /* kernel entry point */ + uint32_t fw_length; /* total length of the firmware */ + uint32_t kernel_ofs; /* kernel data offset */ + uint32_t kernel_len; /* kernel data length */ + uint32_t rootfs_ofs; /* rootfs data offset */ + uint32_t rootfs_len; /* rootfs data length */ + uint32_t boot_ofs; /* bootloader data offset */ + uint32_t boot_len; /* bootloader data length */ + uint8_t pad[360]; +} __attribute__ ((packed)); + +struct fw_v2 { + char fw_version[48]; /* 0x04: fw version string */ + uint32_t hw_id; /* 0x34: hardware id */ + uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */ + uint32_t unk1; /* 0x3c: 0x00000000 */ + uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */ + uint32_t unk2; /* 0x50: 0x00000000 */ + uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */ + uint32_t unk3; /* 0x64: 0xffffffff */ + + uint32_t kernel_la; /* 0x68: kernel load address */ + uint32_t kernel_ep; /* 0x6c: kernel entry point */ + uint32_t fw_length; /* 0x70: total length of the image */ + uint32_t kernel_ofs; /* 0x74: kernel data offset */ + uint32_t kernel_len; /* 0x78: kernel data length */ + uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */ + uint32_t rootfs_len; /* 0x80: rootfs data length */ + uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */ + uint32_t boot_len; /* 0x88: FIXME: seems to be unused */ + uint16_t unk4; /* 0x8c: 0x55aa */ + uint8_t sver_hi; /* 0x8e */ + uint8_t sver_lo; /* 0x8f */ + uint8_t unk5; /* 0x90: magic: 0xa5 */ + uint8_t ver_hi; /* 0x91 */ + uint8_t ver_mid; /* 0x92 */ + uint8_t ver_lo; /* 0x93 */ + uint8_t pad[364]; +} __attribute__ ((packed)); + +struct tplink_fw_header { + uint32_t version; + union { + struct fw_v1 v1; + struct fw_v2 v2; + }; +}; + +static int mtdsplit_parse_tplink(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct tplink_fw_header hdr; + size_t hdr_len, retlen, kernel_size; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + switch (le32_to_cpu(hdr.version)) { + case 1: + if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len); + break; + case 2: + case 3: + if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len); + break; + default: + return -EINVAL; + } + + if (kernel_size > master->size) + return -EINVAL; + + /* Find the rootfs after the kernel. */ + err = mtd_check_rootfs_magic(master, kernel_size); + if (!err) { + rootfs_offset = kernel_size; + } else { + /* + * The size in the header might cover the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset); + if (err) + return err; + } + + parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return TPLINK_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_tplink_parser = { + .owner = THIS_MODULE, + .name = "tplink-fw", + .parse_fn = mtdsplit_parse_tplink, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_tplink_init(void) +{ + register_mtd_parser(&mtdsplit_tplink_parser); + + return 0; +} + +subsys_initcall(mtdsplit_tplink_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c new file mode 100644 index 0000000..6efffce --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org> + * + * 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> + +#include "mtdsplit.h" + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ + +struct trx_header { + __le32 magic; + __le32 len; + __le32 crc32; + __le32 flag_version; + __le32 offset[4]; +}; + +static int +read_trx_header(struct mtd_info *mtd, size_t offset, + struct trx_header *header) +{ + size_t header_len; + size_t retlen; + int ret; + + header_len = sizeof(*header); + ret = mtd_read(mtd, offset, header_len, &retlen, + (unsigned char *) header); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +static int +mtdsplit_parse_trx(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct trx_header hdr; + int nr_parts; + size_t offset; + size_t trx_offset; + size_t trx_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int ret; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + /* find trx image on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + trx_size = 0; + + ret = read_trx_header(master, offset, &hdr); + if (ret) + continue; + + if (hdr.magic != cpu_to_le32(TRX_MAGIC)) { + pr_debug("no valid trx header found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + + trx_size = le32_to_cpu(hdr.len); + if ((offset + trx_size) > master->size) { + pr_debug("trx image exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (trx_size == 0) { + pr_debug("no trx header found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + trx_offset = offset + hdr.offset[0]; + rootfs_offset = offset + hdr.offset[1]; + rootfs_size = master->size - rootfs_offset; + trx_size = rootfs_offset - trx_offset; + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = trx_offset; + parts[0].size = trx_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return nr_parts; + +err: + kfree(parts); + return ret; +} + +static struct mtd_part_parser trx_parser = { + .owner = THIS_MODULE, + .name = "trx-fw", + .parse_fn = mtdsplit_parse_trx, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_trx_init(void) +{ + register_mtd_parser(&trx_parser); + + return 0; +} + +module_init(mtdsplit_trx_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c new file mode 100644 index 0000000..2602f98 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org> + * + * 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> + +#include "mtdsplit.h" + +/* + * uimage_header itself is only 64B, but it may be prepended with another data. + * Currently the biggest size is for Edimax devices: 20B + 64B + */ +#define MAX_HEADER_LEN 84 + +#define IH_MAGIC 0x27051956 /* Image Magic Number */ +#define IH_NMLEN 32 /* Image Name Length */ + +#define IH_OS_LINUX 5 /* Linux */ + +#define IH_TYPE_KERNEL 2 /* OS Kernel Image */ +#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image */ + +/* + * Legacy format image header, + * all data in network byte order (aka natural aka bigendian). + */ +struct uimage_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +}; + +static int +read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid uImage header if found + */ +static int __mtdsplit_parse_uimage(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t uimage_offset; + size_t uimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int uimage_part, rf_part; + int ret; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find uImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct uimage_header *header; + + uimage_size = 0; + + ret = read_uimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid uImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct uimage_header *)(buf + ret); + + uimage_size = sizeof(*header) + be32_to_cpu(header->ih_size); + if ((offset + uimage_size) > master->size) { + pr_debug("uImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (uimage_size == 0) { + pr_debug("no uImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + uimage_offset = offset; + + if (uimage_offset == 0) { + uimage_part = 0; + rf_part = 1; + + /* find the roots after the uImage */ + ret = mtd_find_rootfs_from(master, + uimage_offset + uimage_size, + master->size, + &rootfs_offset); + if (ret) { + pr_debug("no rootfs after uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + uimage_size = rootfs_offset - uimage_offset; + } else { + rf_part = 0; + uimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0); + if (ret) { + pr_debug("no rootfs before uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = uimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[uimage_part].name = KERNEL_PART_NAME; + parts[uimage_part].offset = uimage_offset; + parts[uimage_part].size = uimage_size; + + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t uimage_verify_default(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + + /* default sanity checks */ + if (be32_to_cpu(header->ih_magic) != IH_MAGIC) { + pr_debug("invalid uImage magic: %08x\n", + be32_to_cpu(header->ih_magic)); + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX) { + pr_debug("invalid uImage OS: %08x\n", + be32_to_cpu(header->ih_os)); + return -EINVAL; + } + + if (header->ih_type != IH_TYPE_KERNEL) { + pr_debug("invalid uImage type: %08x\n", + be32_to_cpu(header->ih_type)); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_uimage_parse_generic(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_default); +} + +static struct mtd_part_parser uimage_generic_parser = { + .owner = THIS_MODULE, + .name = "uimage-fw", + .parse_fn = mtdsplit_uimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +#define FW_MAGIC_WNR2000V3 0x32303033 +#define FW_MAGIC_WNR2000V4 0x32303034 +#define FW_MAGIC_WNR2200 0x32323030 +#define FW_MAGIC_WNR612V2 0x32303631 +#define FW_MAGIC_WNR1000V2 0x31303031 +#define FW_MAGIC_WNR1000V2_VC 0x31303030 +#define FW_MAGIC_WNDR3700 0x33373030 +#define FW_MAGIC_WNDR3700V2 0x33373031 + +static ssize_t uimage_verify_wndr3700(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + uint8_t expected_type = IH_TYPE_FILESYSTEM; + + switch be32_to_cpu(header->ih_magic) { + case FW_MAGIC_WNR612V2: + case FW_MAGIC_WNR1000V2: + case FW_MAGIC_WNR1000V2_VC: + case FW_MAGIC_WNR2000V3: + case FW_MAGIC_WNR2200: + case FW_MAGIC_WNDR3700: + case FW_MAGIC_WNDR3700V2: + break; + case FW_MAGIC_WNR2000V4: + expected_type = IH_TYPE_KERNEL; + break; + default: + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX || + header->ih_type != expected_type) + return -EINVAL; + + return 0; +} + +static int +mtdsplit_uimage_parse_netgear(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_wndr3700); +} + +static struct mtd_part_parser uimage_netgear_parser = { + .owner = THIS_MODULE, + .name = "netgear-fw", + .parse_fn = mtdsplit_uimage_parse_netgear, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Edimax + **************************************************/ + +#define FW_EDIMAX_OFFSET 20 +#define FW_MAGIC_EDIMAX 0x43535953 + +static ssize_t uimage_find_edimax(u_char *buf, size_t len) +{ + struct uimage_header *header; + + if (len < FW_EDIMAX_OFFSET + sizeof(*header)) { + pr_err("Buffer too small for checking Edimax header\n"); + return -ENOSPC; + } + + header = (struct uimage_header *)(buf + FW_EDIMAX_OFFSET); + + switch be32_to_cpu(header->ih_magic) { + case FW_MAGIC_EDIMAX: + break; + default: + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX || + header->ih_type != IH_TYPE_FILESYSTEM) + return -EINVAL; + + return FW_EDIMAX_OFFSET; +} + +static int +mtdsplit_uimage_parse_edimax(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_find_edimax); +} + +static struct mtd_part_parser uimage_edimax_parser = { + .owner = THIS_MODULE, + .name = "edimax-fw", + .parse_fn = mtdsplit_uimage_parse_edimax, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_uimage_init(void) +{ + register_mtd_parser(&uimage_generic_parser); + register_mtd_parser(&uimage_netgear_parser); + register_mtd_parser(&uimage_edimax_parser); + + return 0; +} + +module_init(mtdsplit_uimage_init); diff --git a/target/linux/generic/files/drivers/mtd/myloader.c b/target/linux/generic/files/drivers/mtd/myloader.c new file mode 100644 index 0000000..cd57369 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/myloader.c @@ -0,0 +1,182 @@ +/* + * Parse MyLoader-style flash partition tables and produce a Linux partition + * array to match. + * + * Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org> + * + * This file was based on drivers/mtd/redboot.c + * Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> +#include <linux/myloader.h> + +#define BLOCK_LEN_MIN 0x10000 +#define PART_NAME_LEN 32 + +struct part_data { + struct mylo_partition_table tab; + char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN]; +}; + +static int myloader_parse_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct part_data *buf; + struct mylo_partition_table *tab; + struct mylo_partition *part; + struct mtd_partition *mtd_parts; + struct mtd_partition *mtd_part; + int num_parts; + int ret, i; + size_t retlen; + char *names; + unsigned long offset; + unsigned long blocklen; + + buf = vmalloc(sizeof(*buf)); + if (!buf) { + return -ENOMEM; + goto out; + } + tab = &buf->tab; + + blocklen = master->erasesize; + if (blocklen < BLOCK_LEN_MIN) + blocklen = BLOCK_LEN_MIN; + + offset = blocklen; + + /* Find the partition table */ + for (i = 0; i < 4; i++, offset += blocklen) { + printk(KERN_DEBUG "%s: searching for MyLoader partition table" + " at offset 0x%lx\n", master->name, offset); + + ret = mtd_read(master, offset, sizeof(*buf), &retlen, + (void *)buf); + if (ret) + goto out_free_buf; + + if (retlen != sizeof(*buf)) { + ret = -EIO; + goto out_free_buf; + } + + /* Check for Partition Table magic number */ + if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS)) + break; + + } + + if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) { + printk(KERN_DEBUG "%s: no MyLoader partition table found\n", + master->name); + ret = 0; + goto out_free_buf; + } + + /* The MyLoader and the Partition Table is always present */ + num_parts = 2; + + /* Detect number of used partitions */ + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + num_parts++; + } + + mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) + + num_parts * PART_NAME_LEN), GFP_KERNEL); + + if (!mtd_parts) { + ret = -ENOMEM; + goto out_free_buf; + } + + mtd_part = mtd_parts; + names = (char *)&mtd_parts[num_parts]; + + strncpy(names, "myloader", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = 0; + mtd_part->size = offset; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + strncpy(names, "partition_table", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = offset; + mtd_part->size = blocklen; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + if ((buf->names[i][0]) && (buf->names[i][0] != '\xff')) + strncpy(names, buf->names[i], PART_NAME_LEN); + else + snprintf(names, PART_NAME_LEN, "partition%d", i); + + mtd_part->offset = le32_to_cpu(part->addr); + mtd_part->size = le32_to_cpu(part->size); + mtd_part->name = names; + mtd_part++; + names += PART_NAME_LEN; + } + + *pparts = mtd_parts; + ret = num_parts; + + out_free_buf: + vfree(buf); + out: + return ret; +} + +static struct mtd_part_parser myloader_mtd_parser = { + .owner = THIS_MODULE, + .parse_fn = myloader_parse_partitions, + .name = "MyLoader", +}; + +static int __init myloader_mtd_parser_init(void) +{ + register_mtd_parser(&myloader_mtd_parser); + + return 0; +} + +static void __exit myloader_mtd_parser_exit(void) +{ + deregister_mtd_parser(&myloader_mtd_parser); +} + +module_init(myloader_mtd_parser_init); +module_exit(myloader_mtd_parser_exit); + +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); +MODULE_LICENSE("GPL v2"); |