ar71xx: add 64kb bootloader mtd parser for tplinkpart
[oweals/openwrt.git] / target / linux / ar71xx / files / drivers / mtd / tplinkpart.c
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
28 struct 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
51 static struct tplink_fw_header *
52 tplink_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
84 err_free_header:
85         vfree(header);
86 err:
87         return NULL;
88 }
89
90 static 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
111 static 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
176 err_free_parts:
177         kfree(parts);
178 err:
179         *pparts = NULL;
180         return ret;
181 }
182
183 static 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
191 static 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
199 static struct mtd_part_parser tplink_parser = {
200         .owner          = THIS_MODULE,
201         .parse_fn       = tplink_parse_partitions,
202         .name           = "tp-link",
203 };
204
205 static 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
211 static 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
219 module_init(tplink_parser_init);
220
221 MODULE_LICENSE("GPL v2");
222 MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");