tplinkpart.c 5.5 KB
Newer Older
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
/*
 * Copyright (C) 2011 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/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/magic.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/version.h>

#define TPLINK_NUM_PARTS	5
#define TPLINK_HEADER_V1	0x01000000
#define TPLINK_HEADER_V2	0x02000000
#define MD5SUM_LEN		16

#define TPLINK_ART_LEN		0x10000
#define TPLINK_KERNEL_OFFS	0x20000
#define TPLINK_64K_KERNEL_OFFS	0x10000

struct tplink_fw_header {
	uint32_t	version;	/* header version */
	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));

static struct tplink_fw_header *
tplink_read_header(struct mtd_info *mtd, size_t offset)
{
	struct tplink_fw_header *header;
	size_t header_len;
	size_t retlen;
	int ret;
	u32 t;

	header = vmalloc(sizeof(*header));
	if (!header)
		goto err;

	header_len = sizeof(struct tplink_fw_header);
	ret = mtd_read(mtd, offset, header_len, &retlen,
		       (unsigned char *) header);
	if (ret)
		goto err_free_header;

	if (retlen != header_len)
		goto err_free_header;

	/* sanity checks */
	t = be32_to_cpu(header->version);
	if ((t != TPLINK_HEADER_V1) && (t != TPLINK_HEADER_V2))
		goto err_free_header;

	t = be32_to_cpu(header->kernel_ofs);
	if (t != header_len)
		goto err_free_header;

	return header;

err_free_header:
	vfree(header);
err:
	return NULL;
}

static int tplink_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;
}

static int tplink_parse_partitions_offset(struct mtd_info *master,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
				   struct mtd_partition **pparts,
#else
				   const struct mtd_partition **pparts,
#endif
				   struct mtd_part_parser_data *data,
				   size_t offset)
{
	struct mtd_partition *parts;
	struct tplink_fw_header *header;
	int nr_parts;
	size_t art_offset;
	size_t rootfs_offset;
	size_t squashfs_offset;
	int ret;

	nr_parts = TPLINK_NUM_PARTS;
	parts = kzalloc(nr_parts * sizeof(struct mtd_partition), GFP_KERNEL);
	if (!parts) {
		ret = -ENOMEM;
		goto err;
	}

	header = tplink_read_header(master, offset);
	if (!header) {
		pr_notice("%s: no TP-Link header found\n", master->name);
		ret = -ENODEV;
		goto err_free_parts;
	}

	squashfs_offset = offset + sizeof(struct tplink_fw_header) +
			  be32_to_cpu(header->kernel_len);

	ret = tplink_check_rootfs_magic(master, squashfs_offset);
	if (ret == 0)
		rootfs_offset = squashfs_offset;
	else
		rootfs_offset = offset + be32_to_cpu(header->rootfs_ofs);

	art_offset = master->size - TPLINK_ART_LEN;

	parts[0].name = "u-boot";
	parts[0].offset = 0;
	parts[0].size = offset;
	parts[0].mask_flags = MTD_WRITEABLE;

	parts[1].name = "kernel";
	parts[1].offset = offset;
	parts[1].size = rootfs_offset - offset;

	parts[2].name = "rootfs";
	parts[2].offset = rootfs_offset;
	parts[2].size = art_offset - rootfs_offset;

	parts[3].name = "art";
	parts[3].offset = art_offset;
	parts[3].size = TPLINK_ART_LEN;
	parts[3].mask_flags = MTD_WRITEABLE;

	parts[4].name = "firmware";
	parts[4].offset = offset;
	parts[4].size = art_offset - offset;

	vfree(header);

	*pparts = parts;
	return nr_parts;

err_free_parts:
	kfree(parts);
err:
	*pparts = NULL;
	return ret;
}

static int tplink_parse_partitions(struct mtd_info *master,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
				   struct mtd_partition **pparts,
#else
				   const struct mtd_partition **pparts,
#endif
				   struct mtd_part_parser_data *data)
{
	return tplink_parse_partitions_offset(master, pparts, data,
		                              TPLINK_KERNEL_OFFS);
}

static int tplink_parse_64k_partitions(struct mtd_info *master,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
				   struct mtd_partition **pparts,
#else
				   const struct mtd_partition **pparts,
#endif
				   struct mtd_part_parser_data *data)
{
	return tplink_parse_partitions_offset(master, pparts, data,
		                              TPLINK_64K_KERNEL_OFFS);
}

static struct mtd_part_parser tplink_parser = {
	.owner		= THIS_MODULE,
	.parse_fn	= tplink_parse_partitions,
	.name		= "tp-link",
};

static struct mtd_part_parser tplink_64k_parser = {
	.owner		= THIS_MODULE,
	.parse_fn	= tplink_parse_64k_partitions,
	.name		= "tp-link-64k",
};

static int __init tplink_parser_init(void)
{
	register_mtd_parser(&tplink_parser);
	register_mtd_parser(&tplink_64k_parser);

	return 0;
}

module_init(tplink_parser_init);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");