| 1 | /* |
| 2 | * Infineon/ADMTek ADM8668 (WildPass) partition parser support |
| 3 | * |
| 4 | * Copyright (C) 2010 Scott Nicholas <neutronscott@scottn.us> |
| 5 | * Copyright (C) 2012 Florian Fainelli <florian@openwrt.org> |
| 6 | * |
| 7 | * original functions for finding root filesystem from Mike Baker |
| 8 | * |
| 9 | * This program is free software; you can redistribute it and/or modify it |
| 10 | * under the terms of the GNU General Public License as published by the |
| 11 | * Free Software Foundation; either version 2 of the License, or (at your |
| 12 | * option) any later version. |
| 13 | * |
| 14 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 16 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
| 17 | * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| 20 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 21 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | * |
| 25 | * You should have received a copy of the GNU General Public License along |
| 26 | * with this program; if not, write to the Free Software Foundation, Inc., |
| 27 | * 675 Mass Ave, Cambridge, MA 02139, USA. |
| 28 | * |
| 29 | * |
| 30 | * Copyright 2004, Broadcom Corporation |
| 31 | * All Rights Reserved. |
| 32 | * |
| 33 | * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY |
| 34 | * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM |
| 35 | * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS |
| 36 | * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. |
| 37 | */ |
| 38 | |
| 39 | #include <linux/module.h> |
| 40 | #include <linux/types.h> |
| 41 | #include <linux/kernel.h> |
| 42 | #include <linux/sched.h> |
| 43 | #include <linux/wait.h> |
| 44 | #include <linux/mtd/mtd.h> |
| 45 | #include <linux/mtd/partitions.h> |
| 46 | #include <linux/vmalloc.h> |
| 47 | #include <linux/slab.h> |
| 48 | #include <linux/crc32.h> |
| 49 | #include <linux/magic.h> |
| 50 | |
| 51 | #define PFX "adm8668-part: " |
| 52 | |
| 53 | /* first a little bit about the headers i need.. */ |
| 54 | |
| 55 | /* just interested in part of the full struct */ |
| 56 | struct squashfs_super_block { |
| 57 | __le32 s_magic; |
| 58 | __le32 pad0[9]; /* it's not really padding */ |
| 59 | __le64 bytes_used; |
| 60 | }; |
| 61 | |
| 62 | #define IH_MAGIC 0x56190527 /* Image Magic Number */ |
| 63 | struct uboot_header { |
| 64 | uint32_t ih_magic; /* Image Header Magic Number */ |
| 65 | uint32_t ih_hcrc; /* Image Header CRC Checksum */ |
| 66 | uint32_t ih_time; /* Image Creation Timestamp */ |
| 67 | uint32_t ih_size; /* Image Data Size */ |
| 68 | uint32_t ih_load; /* Data Load Address */ |
| 69 | uint32_t ih_ep; /* Entry Point Address */ |
| 70 | uint32_t ih_dcrc; /* Image Data CRC Checksum */ |
| 71 | uint8_t ih_os; /* Operating System */ |
| 72 | uint8_t ih_arch; /* CPU architecture */ |
| 73 | uint8_t ih_type; /* Image Type */ |
| 74 | uint8_t ih_comp; /* Compression Type */ |
| 75 | char ih_name[32]; /* image name */ |
| 76 | }; |
| 77 | |
| 78 | /* in case i wanna change stuff later, and to clarify the math section... */ |
| 79 | #define PART_LINUX 0 |
| 80 | #define PART_ROOTFS 1 |
| 81 | #define PART_UBOOT_ENV 2 |
| 82 | #define NR_PARTS 3 |
| 83 | |
| 84 | static int adm8668_parse_partitions(struct mtd_info *master, |
| 85 | struct mtd_partition **pparts, |
| 86 | struct mtd_part_parser_data *data) |
| 87 | { |
| 88 | int ret; |
| 89 | struct uboot_header uhdr; |
| 90 | int off, blocksize; |
| 91 | size_t len, linux_len; |
| 92 | struct squashfs_super_block shdr; |
| 93 | struct erase_info erase_info; |
| 94 | struct mtd_partition *adm8668_parts; |
| 95 | |
| 96 | memset(&erase_info, 0, sizeof(erase_info)); |
| 97 | |
| 98 | blocksize = master->erasesize; |
| 99 | |
| 100 | if (blocksize < 0x10000) |
| 101 | blocksize = 0x10000; |
| 102 | |
| 103 | adm8668_parts = kzalloc(sizeof(*adm8668_parts) * NR_PARTS, GFP_KERNEL); |
| 104 | if (!adm8668_parts) |
| 105 | return -ENOMEM; |
| 106 | |
| 107 | adm8668_parts[PART_LINUX].name = kstrdup("linux", GFP_KERNEL); |
| 108 | adm8668_parts[PART_LINUX].offset = 0x40000; |
| 109 | adm8668_parts[PART_LINUX].size = master->size - 0x40000; |
| 110 | adm8668_parts[PART_ROOTFS].name = kstrdup("rootfs", GFP_KERNEL); |
| 111 | adm8668_parts[PART_ROOTFS].offset = 0xe0000; |
| 112 | adm8668_parts[PART_ROOTFS].size = 0x140000; |
| 113 | adm8668_parts[PART_UBOOT_ENV].name = kstrdup("uboot_env", GFP_KERNEL); |
| 114 | adm8668_parts[PART_UBOOT_ENV].offset = 0x20000; |
| 115 | adm8668_parts[PART_UBOOT_ENV].size = 0x20000; |
| 116 | |
| 117 | /* now find squashfs */ |
| 118 | memset(&shdr, 0xe5, sizeof(shdr)); |
| 119 | |
| 120 | for (off = 0x40000; off < master->size; off += blocksize) { |
| 121 | /* |
| 122 | * Read into buffer |
| 123 | */ |
| 124 | if (mtd_read(master, off, sizeof(shdr), &len, (char *)&shdr) || |
| 125 | len != sizeof(shdr)) |
| 126 | continue; |
| 127 | |
| 128 | if (shdr.s_magic == SQUASHFS_MAGIC) { |
| 129 | uint32_t fs_size = (uint32_t)shdr.bytes_used; |
| 130 | |
| 131 | pr_info(PFX "filesystem type: squashfs, size=%dkB\n", |
| 132 | fs_size >> 10); |
| 133 | |
| 134 | /* |
| 135 | * Update rootfs based on the superblock info, and |
| 136 | * stretch to end of MTD. rootfs_split will split it |
| 137 | */ |
| 138 | adm8668_parts[PART_ROOTFS].offset = off; |
| 139 | adm8668_parts[PART_ROOTFS].size = master->size - |
| 140 | adm8668_parts[PART_ROOTFS].offset; |
| 141 | |
| 142 | /* |
| 143 | * kernel ends where rootfs starts |
| 144 | * but we'll keep it full-length for upgrades |
| 145 | */ |
| 146 | linux_len = adm8668_parts[PART_LINUX + 1].offset - |
| 147 | adm8668_parts[PART_LINUX].offset; |
| 148 | |
| 149 | adm8668_parts[PART_LINUX].size = master->size - |
| 150 | adm8668_parts[PART_LINUX].offset; |
| 151 | goto found; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | pr_err(PFX "could't find root filesystem\n"); |
| 156 | return NR_PARTS; |
| 157 | |
| 158 | found: |
| 159 | if (mtd_read(master, adm8668_parts[PART_LINUX].offset, sizeof(uhdr), &len, (char *)&uhdr) || |
| 160 | len != sizeof(uhdr)) { |
| 161 | pr_err(PFX "failed to read u-boot header\n"); |
| 162 | return NR_PARTS; |
| 163 | } |
| 164 | |
| 165 | if (uhdr.ih_magic != IH_MAGIC) { |
| 166 | pr_info(PFX "invalid u-boot magic detected?!?!\n"); |
| 167 | return NR_PARTS; |
| 168 | } |
| 169 | |
| 170 | if (be32_to_cpu(uhdr.ih_size) != (linux_len - sizeof(uhdr))) { |
| 171 | u32 data; |
| 172 | size_t data_len = 0; |
| 173 | unsigned char *block; |
| 174 | unsigned int offset; |
| 175 | |
| 176 | offset = adm8668_parts[PART_LINUX].offset + |
| 177 | sizeof(struct uboot_header); |
| 178 | |
| 179 | pr_info(PFX "Updating U-boot image:\n"); |
| 180 | pr_info(PFX " old: [size: %8d crc32: 0x%08x]\n", |
| 181 | be32_to_cpu(uhdr.ih_size), be32_to_cpu(uhdr.ih_dcrc)); |
| 182 | |
| 183 | if (mtd_read(master, offset, sizeof(data), &data_len, (char *)&data)) { |
| 184 | pr_err(PFX "failed to read data\n"); |
| 185 | goto out; |
| 186 | } |
| 187 | |
| 188 | /* Update the data length & crc32 */ |
| 189 | uhdr.ih_size = cpu_to_be32(linux_len - sizeof(uhdr)); |
| 190 | uhdr.ih_dcrc = crc32_le(~0, (char *)&data, linux_len - sizeof(uhdr)) ^ (~0); |
| 191 | uhdr.ih_dcrc = cpu_to_be32(uhdr.ih_dcrc); |
| 192 | |
| 193 | pr_info(PFX " new: [size: %8d crc32: 0x%08x]\n", |
| 194 | be32_to_cpu(uhdr.ih_size), be32_to_cpu(uhdr.ih_dcrc)); |
| 195 | |
| 196 | /* update header's crc... */ |
| 197 | uhdr.ih_hcrc = 0; |
| 198 | uhdr.ih_hcrc = crc32_le(~0, (unsigned char *)&uhdr, |
| 199 | sizeof(uhdr)) ^ (~0); |
| 200 | uhdr.ih_hcrc = cpu_to_be32(uhdr.ih_hcrc); |
| 201 | |
| 202 | /* read first eraseblock from the image */ |
| 203 | block = vmalloc(master->erasesize); |
| 204 | if (!block) |
| 205 | return -ENOMEM; |
| 206 | |
| 207 | if (mtd_read(master, adm8668_parts[PART_LINUX].offset, master->erasesize, &len, block) |
| 208 | || len != master->erasesize) { |
| 209 | pr_err(PFX "error copying first eraseblock\n"); |
| 210 | return 0; |
| 211 | } |
| 212 | |
| 213 | /* Write updated header to the flash */ |
| 214 | memcpy(block, &uhdr, sizeof(uhdr)); |
| 215 | if (master->unlock) |
| 216 | master->unlock(master, off, master->erasesize); |
| 217 | |
| 218 | erase_info.mtd = master; |
| 219 | erase_info.addr = (uint64_t)adm8668_parts[PART_LINUX].offset; |
| 220 | erase_info.len = master->erasesize; |
| 221 | ret = mtd_erase(master, &erase_info); |
| 222 | if (!ret) { |
| 223 | if (mtd_write(master, adm8668_parts[PART_LINUX].offset, master->erasesize, |
| 224 | &len, block)) |
| 225 | pr_err(PFX "write failed"); |
| 226 | } else |
| 227 | pr_err(PFX "erase failed"); |
| 228 | |
| 229 | mtd_sync(master); |
| 230 | out: |
| 231 | if (block) |
| 232 | vfree(block); |
| 233 | pr_info(PFX "done\n"); |
| 234 | } |
| 235 | |
| 236 | *pparts = adm8668_parts; |
| 237 | |
| 238 | return NR_PARTS; |
| 239 | } |
| 240 | |
| 241 | static struct mtd_part_parser adm8668_parser = { |
| 242 | .owner = THIS_MODULE, |
| 243 | .parse_fn = adm8668_parse_partitions, |
| 244 | .name = "adm8668part", |
| 245 | }; |
| 246 | |
| 247 | static int __init adm8668_parser_init(void) |
| 248 | { |
| 249 | return register_mtd_parser(&adm8668_parser); |
| 250 | } |
| 251 | |
| 252 | static void __exit adm8668_parser_exit(void) |
| 253 | { |
| 254 | deregister_mtd_parser(&adm8668_parser); |
| 255 | } |
| 256 | |
| 257 | module_init(adm8668_parser_init); |
| 258 | module_exit(adm8668_parser_exit); |
| 259 | |
| 260 | MODULE_LICENSE("GPL"); |
| 261 | MODULE_AUTHOR("Scott Nicholas <neutronscott@scottn.us>"); |
| 262 | MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); |
| 263 | MODULE_DESCRIPTION("MTD partition parser for ADM8668"); |
| 264 | |