aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_jimage.c
blob: 2b87fe4f8bbd42d9ae1474712bfa821f47919d87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/*
 *  Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com> 
 *
 *  Based on: mtdsplit_uimage.c
 *  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"

#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE )

#define STAG_SIZE 16
#define STAG_ID 0x04
#define STAG_MAGIC 0x2B24

#define SCH2_SIZE 40
#define SCH2_MAGIC 0x2124
#define SCH2_VER 0x02

/*
 * Jboot image header,
 * all data in little endian.
 */

struct jimage_header		//stag + sch2 jboot joined headers
{
	uint8_t stag_cmark;		// in factory 0xFF , in sysupgrade must be the same as stag_id
	uint8_t stag_id;		// 0x04
	uint16_t stag_magic;		//magic 0x2B24
	uint32_t stag_time_stamp;	// timestamp calculated in jboot way
	uint32_t stag_image_length;	// lentgh of kernel + sch2 header
	uint16_t stag_image_checksum;	// negated jboot_checksum of sch2 + kernel
	uint16_t stag_tag_checksum;	// negated jboot_checksum of stag header data
	uint16_t sch2_magic;		// magic 0x2124
	uint8_t sch2_cp_type;	// 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma
	uint8_t sch2_version;	// 0x02 for sch2
	uint32_t sch2_ram_addr;	// ram entry address
	uint32_t sch2_image_len;	// kernel image length
	uint32_t sch2_image_crc32;	// kernel image crc
	uint32_t sch2_start_addr;	// ram start address
	uint32_t sch2_rootfs_addr;	// rootfs flash address
	uint32_t sch2_rootfs_len;	// rootfls length
	uint32_t sch2_rootfs_crc32;	// rootfs crc32
	uint32_t sch2_header_crc32;	// sch2 header crc32, durring calculation this area is replaced by zero
	uint16_t sch2_header_length;	// sch2 header length: 0x28
	uint16_t sch2_cmd_line_length;	// cmd line length, known zeros
};

static int
read_jimage_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_jimage - scan partition and create kernel + rootfs parts
 *
 * @find_header: function to call for a block of data that will return offset
 *      of a valid jImage header if found
 */
static int __mtdsplit_parse_jimage(struct mtd_info *master,
				   const 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 jimage_offset;
	size_t jimage_size = 0;
	size_t rootfs_offset;
	size_t rootfs_size = 0;
	int jimage_part, rf_part;
	int ret;
	enum mtdsplit_part_type type;

	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 jImage on erase block boundaries */
	for (offset = 0; offset < master->size; offset += master->erasesize) {
		struct jimage_header *header;

		jimage_size = 0;

		ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN);
		if (ret)
			continue;

		ret = find_header(buf, MAX_HEADER_LEN);
		if (ret < 0) {
			pr_debug("no valid jImage found in \"%s\" at offset %llx\n",
				 master->name, (unsigned long long) offset);
			continue;
		}
		header = (struct jimage_header *)(buf + ret);

		jimage_size = sizeof(*header) + header->sch2_image_len + ret;
		if ((offset + jimage_size) > master->size) {
			pr_debug("jImage exceeds MTD device \"%s\"\n",
				 master->name);
			continue;
		}
		break;
	}

	if (jimage_size == 0) {
		pr_debug("no jImage found in \"%s\"\n", master->name);
		ret = -ENODEV;
		goto err_free_buf;
	}

	jimage_offset = offset;

	if (jimage_offset == 0) {
		jimage_part = 0;
		rf_part = 1;

		/* find the roots after the jImage */
		ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size,
					   master->size, &rootfs_offset, &type);
		if (ret) {
			pr_debug("no rootfs after jImage in \"%s\"\n",
				 master->name);
			goto err_free_buf;
		}

		rootfs_size = master->size - rootfs_offset;
		jimage_size = rootfs_offset - jimage_offset;
	} else {
		rf_part = 0;
		jimage_part = 1;

		/* check rootfs presence at offset 0 */
		ret = mtd_check_rootfs_magic(master, 0, &type);
		if (ret) {
			pr_debug("no rootfs before jImage in \"%s\"\n",
				 master->name);
			goto err_free_buf;
		}

		rootfs_offset = 0;
		rootfs_size = jimage_offset;
	}

	if (rootfs_size == 0) {
		pr_debug("no rootfs found in \"%s\"\n", master->name);
		ret = -ENODEV;
		goto err_free_buf;
	}

	parts[jimage_part].name = KERNEL_PART_NAME;
	parts[jimage_part].offset = jimage_offset;
	parts[jimage_part].size = jimage_size;

	if (type == MTDSPLIT_PART_TYPE_UBI)
		parts[rf_part].name = UBI_PART_NAME;
	else
		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 jimage_verify_default(u_char *buf, size_t len)
{
	struct jimage_header *header = (struct jimage_header *)buf;

	/* default sanity checks */
	if (header->stag_magic != STAG_MAGIC) {
		pr_debug("invalid jImage stag header magic: %04x\n",
			 header->stag_magic);
		return -EINVAL;
	}
	if (header->sch2_magic != SCH2_MAGIC) {
		pr_debug("invalid jImage sch2 header magic: %04x\n",
			 header->stag_magic);
		return -EINVAL;
	}
	if (header->stag_cmark != header->stag_id) {
		pr_debug("invalid jImage stag header cmark: %02x\n",
			 header->stag_magic);
		return -EINVAL;
	}
	if (header->stag_id != STAG_ID) {
		pr_debug("invalid jImage stag header id: %02x\n",
			 header->stag_magic);
		return -EINVAL;
	}
	if (header->sch2_version != SCH2_VER) {
		pr_debug("invalid jImage sch2 header version: %02x\n",
			 header->stag_magic);
		return -EINVAL;
	}

	return 0;
}

static int
mtdsplit_jimage_parse_generic(struct mtd_info *master,
			      const struct mtd_partition **pparts,
			      struct mtd_part_parser_data *data)
{
	return __mtdsplit_parse_jimage(master, pparts, data,
				      jimage_verify_default);
}

static const struct of_device_id mtdsplit_jimage_of_match_table[] = {
	{ .compatible = "amit,jimage" },
	{},
};

static struct mtd_part_parser jimage_generic_parser = {
	.owner = THIS_MODULE,
	.name = "jimage-fw",
	.of_match_table = mtdsplit_jimage_of_match_table,
	.parse_fn = mtdsplit_jimage_parse_generic,
	.type = MTD_PARSER_TYPE_FIRMWARE,
};

/**************************************************
 * Init
 **************************************************/

static int __init mtdsplit_jimage_init(void)
{
	register_mtd_parser(&jimage_generic_parser);

	return 0;
}

module_init(mtdsplit_jimage_init);