1/*
2 * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
7 *
8 */
9
10#include <linux/kernel.h>
11#include <linux/module.h>
12#include <linux/slab.h>
13#include <linux/vmalloc.h>
14#include <linux/magic.h>
15
16#include <linux/mtd/mtd.h>
17#include <linux/mtd/partitions.h>
18
19#define TPLINK_NUM_PARTS	5
20#define TPLINK_HEADER_V1	0x01000000
21#define TPLINK_HEADER_V2	0x02000000
22#define MD5SUM_LEN		16
23
24#define TPLINK_ART_LEN		0x10000
25#define TPLINK_KERNEL_OFFS	0x20000
26#define TPLINK_64K_KERNEL_OFFS	0x10000
27
28struct tplink_fw_header {
29	uint32_t	version;	/* header version */
30	char		vendor_name[24];
31	char		fw_version[36];
32	uint32_t	hw_id;		/* hardware id */
33	uint32_t	hw_rev;		/* hardware revision */
34	uint32_t	unk1;
35	uint8_t		md5sum1[MD5SUM_LEN];
36	uint32_t	unk2;
37	uint8_t		md5sum2[MD5SUM_LEN];
38	uint32_t	unk3;
39	uint32_t	kernel_la;	/* kernel load address */
40	uint32_t	kernel_ep;	/* kernel entry point */
41	uint32_t	fw_length;	/* total length of the firmware */
42	uint32_t	kernel_ofs;	/* kernel data offset */
43	uint32_t	kernel_len;	/* kernel data length */
44	uint32_t	rootfs_ofs;	/* rootfs data offset */
45	uint32_t	rootfs_len;	/* rootfs data length */
46	uint32_t	boot_ofs;	/* bootloader data offset */
47	uint32_t	boot_len;	/* bootloader data length */
48	uint8_t		pad[360];
49} __attribute__ ((packed));
50
51static struct tplink_fw_header *
52tplink_read_header(struct mtd_info *mtd, size_t offset)
53{
54	struct tplink_fw_header *header;
55	size_t header_len;
56	size_t retlen;
57	int ret;
58	u32 t;
59
60	header = vmalloc(sizeof(*header));
61	if (!header)
62		goto err;
63
64	header_len = sizeof(struct tplink_fw_header);
65	ret = mtd_read(mtd, offset, header_len, &retlen,
66		       (unsigned char *) header);
67	if (ret)
68		goto err_free_header;
69
70	if (retlen != header_len)
71		goto err_free_header;
72
73	/* sanity checks */
74	t = be32_to_cpu(header->version);
75	if ((t != TPLINK_HEADER_V1) && (t != TPLINK_HEADER_V2))
76		goto err_free_header;
77
78	t = be32_to_cpu(header->kernel_ofs);
79	if (t != header_len)
80		goto err_free_header;
81
82	return header;
83
84err_free_header:
85	vfree(header);
86err:
87	return NULL;
88}
89
90static int tplink_check_rootfs_magic(struct mtd_info *mtd, size_t offset)
91{
92	u32 magic;
93	size_t retlen;
94	int ret;
95
96	ret = mtd_read(mtd, offset, sizeof(magic), &retlen,
97		       (unsigned char *) &magic);
98	if (ret)
99		return ret;
100
101	if (retlen != sizeof(magic))
102		return -EIO;
103
104	if (le32_to_cpu(magic) != SQUASHFS_MAGIC &&
105	    magic != 0x19852003)
106		return -EINVAL;
107
108	return 0;
109}
110
111static int tplink_parse_partitions_offset(struct mtd_info *master,
112				   struct mtd_partition **pparts,
113				   struct mtd_part_parser_data *data,
114				   size_t offset)
115{
116	struct mtd_partition *parts;
117	struct tplink_fw_header *header;
118	int nr_parts;
119	size_t art_offset;
120	size_t rootfs_offset;
121	size_t squashfs_offset;
122	int ret;
123
124	nr_parts = TPLINK_NUM_PARTS;
125	parts = kzalloc(nr_parts * sizeof(struct mtd_partition), GFP_KERNEL);
126	if (!parts) {
127		ret = -ENOMEM;
128		goto err;
129	}
130
131	header = tplink_read_header(master, offset);
132	if (!header) {
133		pr_notice("%s: no TP-Link header found\n", master->name);
134		ret = -ENODEV;
135		goto err_free_parts;
136	}
137
138	squashfs_offset = offset + sizeof(struct tplink_fw_header) +
139			  be32_to_cpu(header->kernel_len);
140
141	ret = tplink_check_rootfs_magic(master, squashfs_offset);
142	if (ret == 0)
143		rootfs_offset = squashfs_offset;
144	else
145		rootfs_offset = offset + be32_to_cpu(header->rootfs_ofs);
146
147	art_offset = master->size - TPLINK_ART_LEN;
148
149	parts[0].name = "u-boot";
150	parts[0].offset = 0;
151	parts[0].size = offset;
152	parts[0].mask_flags = MTD_WRITEABLE;
153
154	parts[1].name = "kernel";
155	parts[1].offset = offset;
156	parts[1].size = rootfs_offset - offset;
157
158	parts[2].name = "rootfs";
159	parts[2].offset = rootfs_offset;
160	parts[2].size = art_offset - rootfs_offset;
161
162	parts[3].name = "art";
163	parts[3].offset = art_offset;
164	parts[3].size = TPLINK_ART_LEN;
165	parts[3].mask_flags = MTD_WRITEABLE;
166
167	parts[4].name = "firmware";
168	parts[4].offset = offset;
169	parts[4].size = art_offset - offset;
170
171	vfree(header);
172
173	*pparts = parts;
174	return nr_parts;
175
176err_free_parts:
177	kfree(parts);
178err:
179	*pparts = NULL;
180	return ret;
181}
182
183static int tplink_parse_partitions(struct mtd_info *master,
184				   struct mtd_partition **pparts,
185				   struct mtd_part_parser_data *data)
186{
187	return tplink_parse_partitions_offset(master, pparts, data,
188		                              TPLINK_KERNEL_OFFS);
189}
190
191static int tplink_parse_64k_partitions(struct mtd_info *master,
192				   struct mtd_partition **pparts,
193				   struct mtd_part_parser_data *data)
194{
195	return tplink_parse_partitions_offset(master, pparts, data,
196		                              TPLINK_64K_KERNEL_OFFS);
197}
198
199static struct mtd_part_parser tplink_parser = {
200	.owner		= THIS_MODULE,
201	.parse_fn	= tplink_parse_partitions,
202	.name		= "tp-link",
203};
204
205static struct mtd_part_parser tplink_64k_parser = {
206	.owner		= THIS_MODULE,
207	.parse_fn	= tplink_parse_64k_partitions,
208	.name		= "tp-link-64k",
209};
210
211static int __init tplink_parser_init(void)
212{
213	register_mtd_parser(&tplink_parser);
214	register_mtd_parser(&tplink_64k_parser);
215
216	return 0;
217}
218
219module_init(tplink_parser_init);
220
221MODULE_LICENSE("GPL v2");
222MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
223