1/* 2 * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org> 3 * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name> 4 * 5 * This program is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 as published 7 * by the Free Software Foundation. 8 * 9 */ 10 11#include <linux/module.h> 12#include <linux/init.h> 13#include <linux/kernel.h> 14#include <linux/slab.h> 15#include <linux/mtd/mtd.h> 16#include <linux/mtd/partitions.h> 17#include <linux/byteorder/generic.h> 18 19#include "mtdsplit.h" 20 21#define TPLINK_NR_PARTS 2 22#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ 23 24#define MD5SUM_LEN 16 25 26struct fw_v1 { 27 char vendor_name[24]; 28 char fw_version[36]; 29 uint32_t hw_id; /* hardware id */ 30 uint32_t hw_rev; /* hardware revision */ 31 uint32_t unk1; 32 uint8_t md5sum1[MD5SUM_LEN]; 33 uint32_t unk2; 34 uint8_t md5sum2[MD5SUM_LEN]; 35 uint32_t unk3; 36 uint32_t kernel_la; /* kernel load address */ 37 uint32_t kernel_ep; /* kernel entry point */ 38 uint32_t fw_length; /* total length of the firmware */ 39 uint32_t kernel_ofs; /* kernel data offset */ 40 uint32_t kernel_len; /* kernel data length */ 41 uint32_t rootfs_ofs; /* rootfs data offset */ 42 uint32_t rootfs_len; /* rootfs data length */ 43 uint32_t boot_ofs; /* bootloader data offset */ 44 uint32_t boot_len; /* bootloader data length */ 45 uint8_t pad[360]; 46} __attribute__ ((packed)); 47 48struct fw_v2 { 49 char fw_version[48]; /* 0x04: fw version string */ 50 uint32_t hw_id; /* 0x34: hardware id */ 51 uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */ 52 uint32_t unk1; /* 0x3c: 0x00000000 */ 53 uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */ 54 uint32_t unk2; /* 0x50: 0x00000000 */ 55 uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */ 56 uint32_t unk3; /* 0x64: 0xffffffff */ 57 58 uint32_t kernel_la; /* 0x68: kernel load address */ 59 uint32_t kernel_ep; /* 0x6c: kernel entry point */ 60 uint32_t fw_length; /* 0x70: total length of the image */ 61 uint32_t kernel_ofs; /* 0x74: kernel data offset */ 62 uint32_t kernel_len; /* 0x78: kernel data length */ 63 uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */ 64 uint32_t rootfs_len; /* 0x80: rootfs data length */ 65 uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */ 66 uint32_t boot_len; /* 0x88: FIXME: seems to be unused */ 67 uint16_t unk4; /* 0x8c: 0x55aa */ 68 uint8_t sver_hi; /* 0x8e */ 69 uint8_t sver_lo; /* 0x8f */ 70 uint8_t unk5; /* 0x90: magic: 0xa5 */ 71 uint8_t ver_hi; /* 0x91 */ 72 uint8_t ver_mid; /* 0x92 */ 73 uint8_t ver_lo; /* 0x93 */ 74 uint8_t pad[364]; 75} __attribute__ ((packed)); 76 77struct tplink_fw_header { 78 uint32_t version; 79 union { 80 struct fw_v1 v1; 81 struct fw_v2 v2; 82 }; 83}; 84 85static int mtdsplit_parse_tplink(struct mtd_info *master, 86 struct mtd_partition **pparts, 87 struct mtd_part_parser_data *data) 88{ 89 struct tplink_fw_header hdr; 90 size_t hdr_len, retlen, kernel_size; 91 size_t rootfs_offset; 92 struct mtd_partition *parts; 93 int err; 94 95 hdr_len = sizeof(hdr); 96 err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); 97 if (err) 98 return err; 99 100 if (retlen != hdr_len) 101 return -EIO; 102 103 switch (le32_to_cpu(hdr.version)) { 104 case 1: 105 if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr)) 106 return -EINVAL; 107 108 kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len); 109 break; 110 case 2: 111 case 3: 112 if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr)) 113 return -EINVAL; 114 115 kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len); 116 break; 117 default: 118 return -EINVAL; 119 } 120 121 if (kernel_size > master->size) 122 return -EINVAL; 123 124 /* Find the rootfs after the kernel. */ 125 err = mtd_check_rootfs_magic(master, kernel_size, NULL); 126 if (!err) { 127 rootfs_offset = kernel_size; 128 } else { 129 /* 130 * The size in the header might cover the rootfs as well. 131 * Start the search from an arbitrary offset. 132 */ 133 err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS, 134 master->size, &rootfs_offset, NULL); 135 if (err) 136 return err; 137 } 138 139 parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL); 140 if (!parts) 141 return -ENOMEM; 142 143 parts[0].name = KERNEL_PART_NAME; 144 parts[0].offset = 0; 145 parts[0].size = rootfs_offset; 146 147 parts[1].name = ROOTFS_PART_NAME; 148 parts[1].offset = rootfs_offset; 149 parts[1].size = master->size - rootfs_offset; 150 151 *pparts = parts; 152 return TPLINK_NR_PARTS; 153} 154 155static struct mtd_part_parser mtdsplit_tplink_parser = { 156 .owner = THIS_MODULE, 157 .name = "tplink-fw", 158 .parse_fn = mtdsplit_parse_tplink, 159 .type = MTD_PARSER_TYPE_FIRMWARE, 160}; 161 162static int __init mtdsplit_tplink_init(void) 163{ 164 register_mtd_parser(&mtdsplit_tplink_parser); 165 166 return 0; 167} 168 169subsys_initcall(mtdsplit_tplink_init); 170