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