ar71xx: add NAND driver for the Mikrotik RB91x boards
[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 MD5SUM_LEN              16
22
23 #define TPLINK_ART_LEN          0x10000
24 #define TPLINK_KERNEL_OFFS      0x20000
25
26 struct tplink_fw_header {
27         uint32_t        version;        /* header version */
28         char            vendor_name[24];
29         char            fw_version[36];
30         uint32_t        hw_id;          /* hardware id */
31         uint32_t        hw_rev;         /* hardware revision */
32         uint32_t        unk1;
33         uint8_t         md5sum1[MD5SUM_LEN];
34         uint32_t        unk2;
35         uint8_t         md5sum2[MD5SUM_LEN];
36         uint32_t        unk3;
37         uint32_t        kernel_la;      /* kernel load address */
38         uint32_t        kernel_ep;      /* kernel entry point */
39         uint32_t        fw_length;      /* total length of the firmware */
40         uint32_t        kernel_ofs;     /* kernel data offset */
41         uint32_t        kernel_len;     /* kernel data length */
42         uint32_t        rootfs_ofs;     /* rootfs data offset */
43         uint32_t        rootfs_len;     /* rootfs data length */
44         uint32_t        boot_ofs;       /* bootloader data offset */
45         uint32_t        boot_len;       /* bootloader data length */
46         uint8_t         pad[360];
47 } __attribute__ ((packed));
48
49 static struct tplink_fw_header *
50 tplink_read_header(struct mtd_info *mtd, size_t offset)
51 {
52         struct tplink_fw_header *header;
53         size_t header_len;
54         size_t retlen;
55         int ret;
56         u32 t;
57
58         header = vmalloc(sizeof(*header));
59         if (!header)
60                 goto err;
61
62         header_len = sizeof(struct tplink_fw_header);
63         ret = mtd_read(mtd, offset, header_len, &retlen,
64                        (unsigned char *) header);
65         if (ret)
66                 goto err_free_header;
67
68         if (retlen != header_len)
69                 goto err_free_header;
70
71         /* sanity checks */
72         t = be32_to_cpu(header->version);
73         if (t != TPLINK_HEADER_V1)
74                 goto err_free_header;
75
76         t = be32_to_cpu(header->kernel_ofs);
77         if (t != header_len)
78                 goto err_free_header;
79
80         return header;
81
82 err_free_header:
83         vfree(header);
84 err:
85         return NULL;
86 }
87
88 static int tplink_check_rootfs_magic(struct mtd_info *mtd, size_t offset)
89 {
90         u32 magic;
91         size_t retlen;
92         int ret;
93
94         ret = mtd_read(mtd, offset, sizeof(magic), &retlen,
95                        (unsigned char *) &magic);
96         if (ret)
97                 return ret;
98
99         if (retlen != sizeof(magic))
100                 return -EIO;
101
102         if (le32_to_cpu(magic) != SQUASHFS_MAGIC &&
103             magic != 0x19852003)
104                 return -EINVAL;
105
106         return 0;
107 }
108
109 static int tplink_parse_partitions(struct mtd_info *master,
110                                    struct mtd_partition **pparts,
111                                    struct mtd_part_parser_data *data)
112 {
113         struct mtd_partition *parts;
114         struct tplink_fw_header *header;
115         int nr_parts;
116         size_t offset;
117         size_t art_offset;
118         size_t rootfs_offset;
119         size_t squashfs_offset;
120         int ret;
121
122         nr_parts = TPLINK_NUM_PARTS;
123         parts = kzalloc(nr_parts * sizeof(struct mtd_partition), GFP_KERNEL);
124         if (!parts) {
125                 ret = -ENOMEM;
126                 goto err;
127         }
128
129         offset = TPLINK_KERNEL_OFFS;
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 struct mtd_part_parser tplink_parser = {
184         .owner          = THIS_MODULE,
185         .parse_fn       = tplink_parse_partitions,
186         .name           = "tp-link",
187 };
188
189 static int __init tplink_parser_init(void)
190 {
191         return register_mtd_parser(&tplink_parser);
192 }
193
194 module_init(tplink_parser_init);
195
196 MODULE_LICENSE("GPL v2");
197 MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");